Java tutorial
/* * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.infrautils.utils.mdc; import com.google.common.annotations.Beta; import com.google.common.base.Strings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.Locale; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; /** * Execution Origin utility. * * <p>An "Execution Origin" identifies the first (original) * point of contact of an external event entering the system. That first event * typically leads to numerous subsequent internal events in a system, and the * point of such an ID is (only) to be able to correlate a series of such * internal events among themselves and back to the original first external * event. All log messages automatically include this ID. * * <p>In the case of OpenDaylight, such "origins" include HTTP requests (which are * often from the nortbound APIs exposed by YANG RESTCONF, but not limited to), * or something like an incoming Thrift request from a router, or an OVSDB notification, etc. * * <p>An Origin ID is "unique" within a certain given time span, which is typically "long enough" * to correlate events. However it may "overflow" and repeat, at some point in time. This is * dependent on the number of events (and thus the load of the system). This implementation * issues an INFO level log indicating when such an overflow occurred. * * @author Michael Vorburger.ch */ @Beta public final class ExecutionOrigin extends MDCEntry { private static final long serialVersionUID = 1L; private static final Logger LOG = LoggerFactory.getLogger(ExecutionOrigin.class); /** * The String to be used as key to {@link MDCs#put(String, String)} an Origin ID * into the MDC. Note that users of this will just call {@link #mdcKeyString()} * to get this value, instead of using this constant directly. Note also that * the Log4j configuration has this constant hard-coded, to include it in logs. */ private static final String MDC_KEY = "originID"; private static final AtomicLong NEXT_ID = new AtomicLong(); // base 32 is chosen because the implementation is faster than a higher one // (bigger ones internally use BigInteger, in Java 8 at least), and because // using e.g. 36 (for 26 letter plus 10 digits..) the max. long would give // "3W5E11264SGSE" instead of "FVVVVVVVVVVVU" (in base 32) - but both are 13 chars // long, so a higher base would just make it slower without getting us a // shorter String ID (and the shorter the better, in logs) private static final int RADIX = 32; private static final int ID_STRING_MAX_LENGTH = 13; /** * Returns the next origin ID. * * <p>This method does <b>NOT</b> put that next value into the MDC; * doing that, as well as (crucially) cleaning it up again at some point, * is the responsibility of the caller of this method, typically using * one of {@link MDCs}' methods. */ public static ExecutionOrigin next() { long nextId = NEXT_ID.getAndIncrement(); if (nextId == 0) { LOG.info("Origin ID is [re]starting at 0 (either the system just started, or it has now overflown)"); } return new ExecutionOrigin(nextId); } /** * Returns the current origin ID. * * <p>Normal application code will never have to invoke this. Do <b>NOT</b> explicitly use this in your * Logger; the origin ID will already be automatically included in <b>all ODL logs</b>, always. * * <p>Typical usage of this method would only be in order to e.g. propagate the current origin ID onwards * to another system during an RPC call; if that other system has it's own tracing facility similar to * this, it will make it possible to correlate events. * * <p>This method is really just a convenience short-cut around <code>MDC.get(MDC_KEY)</code>, * and can be used for more readable code. It also does throw a clear error message instead of returning null. * * @throws IllegalStateException if there is no Origin ID set in the MDC */ public static String currentID() { String originID = MDC.get(MDC_KEY); if (originID == null) { throw new IllegalStateException("No Origin ID available in MDC :("); } return originID; } // package-private!! Only ever intended to be used in the unit test of this class static void resetOriginID_used_only_for_testing(long newOriginID) { NEXT_ID.set(newOriginID); } private final long id; @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") // OK and intentional, as null check in mdcValueString() private transient String idAsString; private ExecutionOrigin(long id) { this.id = id; } @Override public String mdcKeyString() { return MDC_KEY; } /** * The exact internal implementation format of this ID may change over time, and * should not be relied upon. It currently is a char '0' padded base-32 encoded * atomically incremented 64 bit long. */ @Override public String mdcValueString() { if (idAsString == null) { String nextIdString = Long.toUnsignedString(id, RADIX).toUpperCase(Locale.ENGLISH); String paddedNextIdString = Strings.padStart(nextIdString, ID_STRING_MAX_LENGTH, '0'); this.idAsString = paddedNextIdString; } return idAsString; } @Override public int hashCode() { int prime = 31; return prime + (int) (id ^ id >>> 32); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof ExecutionOrigin)) { return false; } ExecutionOrigin other = (ExecutionOrigin) obj; if (id != other.id) { return false; } return true; } }