io.mats3.intercept.logging.MatsMetricsLoggingInterceptor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mats-intercept-logging Show documentation
Show all versions of mats-intercept-logging Show documentation
Mats^3 interceptor for structured logging over SLF4J, adding several data points using the SLF4J MDC for each initiation, message receive and message send.
package io.mats3.intercept.logging;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import io.mats3.MatsFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import io.mats3.MatsEndpoint.DetachedProcessContext;
import io.mats3.MatsEndpoint.ProcessContext;
import io.mats3.MatsFactory.FactoryConfig;
import io.mats3.MatsInitiator;
import io.mats3.MatsInitiator.MatsInitiate;
import io.mats3.api.intercept.CommonCompletedContext;
import io.mats3.api.intercept.CommonCompletedContext.MatsMeasurement;
import io.mats3.api.intercept.CommonCompletedContext.MatsTimingMeasurement;
import io.mats3.api.intercept.MatsInitiateInterceptor;
import io.mats3.api.intercept.MatsLoggingInterceptor;
import io.mats3.api.intercept.MatsMetricsInterceptor;
import io.mats3.api.intercept.MatsOutgoingMessage.MatsSentOutgoingMessage;
import io.mats3.api.intercept.MatsOutgoingMessage.MessageType;
import io.mats3.api.intercept.MatsStageInterceptor;
import io.mats3.api.intercept.MatsStageInterceptor.StageCompletedContext.StageProcessResult;
/**
* A logging interceptor that writes loglines to two SLF4J loggers, including multiple pieces of information on the MDC
* (initiatorId, endpointId and stageIds, and timings and sizes), so that it hopefully is easy to reason about and debug
* all Mats flows, and to be able to use the logging system (e.g. Kibana over ElasticSearch) to create statistics.
*
* Two loggers are used, which are {@link #log_init "io.mats3.log.init"} and {@link #log_stage "io.mats3.log.stage"}.
* All loglines' message-part is prepended with "#MATSLOG#"
.
*
* Log lines and their metadata
There are 5 different type of log lines emitted by this logging interceptor -
* but note that the "Per Message" log line can be combined with the "Initiate Completed" or "Stage Completed" loglines
* if the initiation or stage only produce a single message - read more at end.
*
* - Initiate Completed
* - Message Received (on Stage)
* - Stage Completed
* - Per Created/Sent message (both for initiations and stage produced messages)
* - Metrics set from the user lambda during initiation and stages
*
* Some MDC properties are set by the JMS implementation, and not by this interceptor. These are the ones that do not
* have a JavaDoc-link in the below listings.
*
* Note that all loglines that are emitted by any code, user or system, which are within Mats init or processing,
* will have the follow properties set:
*
* "mats.AppName"
: The app-name which the MatsFactory was created with
* "mats.AppVersion"
: The app-version which the MatsFactory was created with
* - Either, or both, of
"mats.Init"
(set to 'true' on initiation enter, and cleared on exit) and
* "mats.Stage"
(set fixed to 'true' for all Mats Stage processors). If initiation within a stage,
* both are set.
* "mats.CallNo"
: The "Call Number", which starts at 0 for the initiation, and then 1 for first
* stage processing. (It refers to the "calls", i.e. passed messages.)
* - For Stage processors:
"mats.StageId"
: Set fixed to the stageId for all Mats Stage
* processors.
* - For Stage processors:
"mats.StageIndex"
: Set fixed to the stage index for all Mats Stage
* processors - '0' for the initial Stage, '1' for stage1 etc.
*
*
*
* MDC Properties for Initiate Completed:
Notice: Initiate Completed is rather similar to Stage Completed.
*
* - {@link #MDC_MATS_INITIATE_COMPLETED "mats.InitiateCompleted"}: Present on a single logline per
* completed initiation - can be used to count initiations. The value is same as {@link #MDC_MATS_EXEC_TIME_TOTAL
* "mats.exec.Total.ms"}, see Metrics below. (Assuming each initiation produces one message, and hence one flow, this
* count should be identical to the count of {@link #MDC_MATS_FLOW_COMPLETED}. However, an initiation can produce
* multiple messages (or zero), as described in {@link #MDC_MATS_EXEC_QUANTITY_OUT}, thus if you sum the quantity value
* of lines that have this property set, the result should actually be identical to flows completed.)
* - {@link #MDC_INIT_OR_STAGE_ID "mats.InitOrStageId"}: The initiatorId ("fromId") from the first
* (typically sole) message in an initiation. It should be common that all messages in a single initiation have the same
* initiatorId. If there are no sent messages, the value for an initiation will be
* {@link MatsMetricsInterceptor#INITIATOR_ID_WHEN_NO_OUTGOING_MESSAGES "_no_outgoing_messages_"}. This property is also
* set on the sent messages. For an initiation, this will be identical to {@link #MDC_MATS_INIT_ID "mats.init.Id"}, see
* below.
* - {@link #MDC_TRACE_ID "traceId"}: (Flow prop) Set for an initiation from when it is set in the user
* code performing the initiation (reset to whatever it was upon exit of initiation lambda)
* - {@link #MDC_MATS_INIT_APP "mats.init.App"}: (Flow prop) For an initiation, this is the current app,
* so identical to
"mats.AppName"
.
* - {@link #MDC_MATS_INIT_ID "mats.init.Id"}: (Flow prop) For an initiation, this will be identical to
* {@link #MDC_INIT_OR_STAGE_ID "mats.InitOrStageId"}, see above.
* - {@link #MDC_MATS_INTERACTIVE "mats.Interactive"}: (Flow prop) This will be the value of the
* interactive flag of the first (typically sole) message of an initiation. It should be common that all messages in a
* single initiation have the same value. If there are no sent messages, the value for an initiation will be
*
false
.
*
* Metrics for the execution of the initiation (very similar to the metrics for a stage processing):
*
* - {@link #MDC_MATS_VERSION "mats.Version"}: Mats implementation version, as gotten by
* {@link FactoryConfig#getMatsImplementationVersion()}.
* - {@link #MDC_MATS_EXEC_TIME_TOTAL "mats.exec.Total.ms"}: Total time taken for the initiation to complete -
* including both user code and all system code including commits. The same value is present on
* {@link #MDC_MATS_INITIATE_COMPLETED "mats.InitiateCompleted"}, see above.
* - {@link #MDC_MATS_EXEC_TIME_USER_LAMBDA "mats.exec.UserLambda.ms"}: Part of total time taken for the actual
* user lambda, including e.g. any external IO like DB, but excluding all system code, in particular message creation,
* and commits.
* - {@link #MDC_MATS_EXEC_TIME_OUT "mats.exec.Out.ms"}: Part of total time taken for the creation and
* serialization of Mats messages, and production and sending of "message system messages" (e.g. creating and
* populating JMS Message plus
jmsProducer.send(..)
for the JMS implementation).
* - {@link #MDC_MATS_EXEC_QUANTITY_OUT "mats.exec.Out.quantity"}: Number of messages sent in this initiation.
* Should most often be 1, but can be multiple, and also zero.
* - {@link #MDC_MATS_EXEC_SIZE_OUT_TOTAL_WIRE "mats.exec.Out.TotalWire.bytes"}: The sum of
* {@link #MDC_MATS_OUT_SIZE_TOTAL_WIRE "mats.out.TotalWire.bytes"} for all messages sent in this initiation.
* - {@link #MDC_MATS_EXEC_TIME_DB_COMMIT "mats.exec.DbCommit.ms"}: Part of total time taken for committing
* DB.
* - {@link #MDC_MATS_EXEC_TIME_MSGSYS_COMMIT "mats.exec.MsgSysCommit.ms"}: Part of total time taken for
* committing the message system (e.g.
jmsSession.commit()
for the JMS implementation)
*
* User metrics: Furthermore, any metrics (measurements and timings) set from within an initiation or stage will
* be available as separate log lines, the metric being set on the MDC. The MDC-key for timings will be
* {@link #MDC_MATS_EXEC_OPS_TIMING_PREFIX "mats.exec.ops.time."}+{metricId}+".ms"
and for measurements
* {@link #MDC_MATS_EXEC_OPS_MEASURE_PREFIX "mats.exec.ops.measure."}+{metricId} + '.' + {baseUnit}
. If
* labels/tags are set on a metric, the MDC-key will be {metric MDC-key} + ".tag." + {labelKey}
. The name
* of the constructed metric MDC-key is made available as value of the MDC-key {@link #MDC_MATS_EXEC_OPS_KEYNAME
* "mats.exec.ops.key"}, this so that the metrics keys employed can be found more easily by searching for this static
* key (Elastic have no easy way to find key names in an index using the query language itself). The description of the
* metric is available under the MDC-key {@link #MDC_MATS_EXEC_OPS_DESCRIPTION "mats.exec.ops.description"}.
*
*
* MDC Properties for Message Received:
*
* - {@link #MDC_MATS_MESSAGE_RECEIVED "mats.MessageReceived"}: Present on a single logline per received
* message - can be used to count received messages. (This count should be identical to the count of
* {@link #MDC_MATS_STAGE_COMPLETED}). The value is the same as {@link #MDC_MATS_IN_TIME_TOTAL_PREPROC_AND_DESERIAL
* "mats.in.TotalPreprocDeserial.ms"}, see Metrics below.
* "mats.StageId"
: Always set on the Processor threads for a stage, so any logline output inside
* a Mats stage will have this set.
* - {@link #MDC_TRACE_ID "traceId"}: The Mats flow's traceId, set from the initiation.
* "mats.in.MsgSysId"
: The messageId the messaging system assigned the message when it was
* produced on the sender side (e.g JMSMessageID for the JMS implementation)
* - {@link #MDC_MATS_IN_MATS_MESSAGE_ID "mats.in.MatsMsgId"}: The messageId the Mats system assigned the
* message when it was produced on the sender side. Note that it consists of the Mats flow id + an individual part per
* message in the flow.
* - {@link #MDC_MATS_IN_MESSAGE_TYPE "mats.in.MessageType"}: The {@link MessageType MessageType} of the
* incoming message, for example "SEND", "REQUEST" or "REPLY".
* - {@link #MDC_MATS_IN_FROM_APP_NAME "mats.in.from.App"}: Which app this incoming message is from.
* - {@link #MDC_MATS_IN_FROM_ID "mats.in.from.Id"}: Which initiatorId, endpointId or stageId this message is
* from.
* - NOTICE: NOT using
"mats.in.to.app"
, as that is "this" App, and thus identical to
* "mats.AppName"
.
* - NOTICE: NOT using
"mats.in.to.id"
, as that is "this" StageId, and thus identical to
* "mats.StageId"
.
*
* "Flow properties", i.e. common for all received and sent messages in a Mats flow (initiated, received on stage and
* sent from stage):
*
* - {@link #MDC_MATS_INIT_APP "mats.init.App"}: Which App initiated this Mats flow.
* - {@link #MDC_MATS_INIT_ID "mats.init.Id"}: The initiatorId of this MatsFlow;
*
matsInitiate.from(initiatorId)
.
* - {@link #MDC_MATS_AUDIT "mats.Audit"}: Whether this Mats flow should be audited.
* - {@link #MDC_MATS_INTERACTIVE "mats.Interactive"}: Whether this Mats flow should be treated as
* "interactive", meaning that a human is actively waiting for its execution.
* - {@link #MDC_MATS_PERSISTENT "mats.Persistent"}: Whether the messaging system should use persistent (as
* opposed to non-persistent) message passing, i.e. store to disk to survive a crash.
*
* Metrics for message reception (note how these compare to the production of a message, on the "Per Message"
* loglines):
*
* - {@link #MDC_MATS_IN_TIME_SINCE_SENT "mats.in.SinceSent.ms"}: Time taken from message was sent to it was
* received. This metric gives the queue time, plus any other latency wrt. sending and committing on the sending side
* and the transfer and reception on the receiving side.This metric is susceptible to time skews between
* nodes.
* - {@link #MDC_MATS_IN_TIME_SINCE_PRECEDING_ENDPOINT_STAGE "mats.in.PrecedEpStage.ms"}: Time taken from the
* sending of a message from the Stage immediately preceding this Stage on the same Endpoint, to the reception of
* a message on this Stage, i.e. the time between stages of an Endpoint (but also between an Initiation REQUEST and the
* replyTo-reception on the Terminator). This timing includes queue times and processing times of requested endpoints
* happening in between the send and the receive, as well as any other latencies. For example, it is the time between
* when EndpointA.Stage2 performs a REQUEST to AnotherEndpointB, till the REPLY from that endpoint is received on
* EndpointA.Stage3 (There might be dozens of message passing and processings in between those two stages of the
* same endpoint, as AnotherEndpointB might itself have a dozen stages, each performing some requests to yet other
* endpoints). This metric is susceptible to time skews between nodes.
* - {@link #MDC_MATS_IN_TIME_TOTAL_PREPROC_AND_DESERIAL "mats.in.TotalPreprocDeserial.ms"}: Total time taken
* to preprocess and deserialize the incoming message. The same value is present on {@link #MDC_MATS_MESSAGE_RECEIVED
* "mats.MessageReceived"}, see above.
* - {@link #MDC_MATS_IN_TIME_MSGSYS_DECONSTRUCT "mats.in.MsgSysDeconstruct.ms"}: Part of total time taken to
* pick out the Mats pieces from the incoming message system message.
* - {@link #MDC_MATS_IN_SIZE_ENVELOPE_WIRE "mats.in.EnvelopeWire.bytes"}: How big the incoming Mats envelope
* ("MatsTrace") was in the incoming message system message, i.e. "on the wire".
* - {@link #MDC_MATS_IN_TIME_ENVELOPE_DECOMPRESS "mats.in.EnvelopeDecompress.ms"}: Part of total time taken to
* decompress the Mats envelope (will be 0 if it was sent plain, and > 0 if it was compressed).
* - {@link #MDC_MATS_IN_SIZE_ENVELOPE_SERIAL "mats.in.EnvelopeSerial.bytes"}: How big the incoming Mats
* envelope ("MatsTrace") is in its serialized form, after decompression.
* - {@link #MDC_MATS_IN_TIME_ENVELOPE_DESERIAL "mats.in.EnvelopeDeserial.ms"}: Part of total time taken to
* deserialize the incoming serialized Mats envelope.
* - {@link #MDC_MATS_IN_TIME_DATA_AND_STATE_DESERIAL "mats.in.DataAndStateDeserial.ms"}: Part of total time
* taken to deserialize the data and state objects (DTO and STO) from the Mats envelope.
* - {@link #MDC_MATS_IN_SIZE_TOTAL_WIRE "mats.in.TotalWire.bytes"}: Best approximation of the total message
* system wire size (envelope (compressed) + sideloads + any meta info set on the message). The overhead of the message
* system itself might not be included, unless actual message size is directly available from message system.
* - {@link #MDC_MATS_IN_SIZE_STATE_SERIAL "mats.in.StateSerial.bytes"}: The serialized size of the incoming
* state object (STO). If there is no incoming state,
0
is returned - this is normal for initial stage of
* an endpoint, unless an initiation sets initialState. If the serializer employs Strings, the returned value is
* String.length()
, which might not exactly be the number of bytes depending on the String contents.
* - {@link #MDC_MATS_IN_SIZE_DATA_SERIAL "mats.in.DataSerial.bytes"}: The serialized size of the incoming data
* object (DTO). Note that
null
might not return 0
bytes, depending on how the serializer
* works. If the serializer employs Strings, the returned value is String.length()
, which might not exactly
* be the number of bytes depending on the String contents.
*
*
*
* MDC Properties for Stage Completed:
Notice: Stage Completed is rather similar to Initiate Completed.
*
* - {@link #MDC_MATS_STAGE_COMPLETED "mats.StageCompleted"}: Present on a single logline per completed stage -
* can be used to count stage processings. (This count should be identical to the count of
* {@link #MDC_MATS_MESSAGE_RECEIVED}.) The value is same as {@link #MDC_MATS_EXEC_TIME_TOTAL "mats.exec.Total.ms"},
* see Metrics below.
* "mats.StageId"
: Always set on the Processor threads for a stage, so any logline output inside
* a Mats stage will have this set.
* - {@link #MDC_MATS_PROCESS_RESULT "mats.ProcessResult"}: the ProcessResult enum
* - {@link #MDC_INIT_OR_STAGE_ID "mats.InitOrStageId"}: The StageId for the completed stage, thus identical to
* "mats.StageId". The rationale for having this one more time is so that you can search for it across initiation
* complete and stage complete, using a common key, as they are rather similar processes. Note that it is also set on
* message sending.
* - {@link #MDC_TRACE_ID "traceId"}: (Flow prop) The Mats flow's traceId, set from the initiation.
* - {@link #MDC_MATS_INIT_APP "mats.init.App"}: (Flow prop) The application that initiated this Mats
* Flow.
* - {@link #MDC_MATS_INIT_ID "mats.init.Id"}: (Flow prop) The initiatorId for this Mats Flow, i.e. the
* value of {@link MatsInitiate#from(String) matsInitiate.from(initiatorId)}.
* - {@link #MDC_MATS_INTERACTIVE "mats.Interactive"}: (Flow prop) The value of the interactive flag for
* this Mats Flow.
*
* Metrics for the processing of the stage (very similar to the metrics for an initiation execution, but includes
* metrics for the reception of a message):
*
* - {@link #MDC_MATS_VERSION "mats.Version"}: Same as for initiation.
* - {@link #MDC_MATS_EXEC_TIME_TOTAL "mats.exec.Total.ms"}: Total time taken for the stage to complete -
* including both user code and all system code including commits. The same value is present on
* {@link #MDC_MATS_STAGE_COMPLETED "mats.StageCompleted"}, see above.
* - {@link #MDC_MATS_EXEC_TIME_TOTAL_PREPROC_AND_DESERIAL "mats.exec.TotalPreprocDeserial.ms"}: Part of the
* total time taken for the preprocessing and deserialization of the incoming message, same value as the message
* received logline's {@link #MDC_MATS_IN_TIME_TOTAL_PREPROC_AND_DESERIAL "mats.in.TotalPreprocDeserial.ms"}, as that
* piece is also part of the stage processing.
* - {@link #MDC_MATS_EXEC_SIZE_IN_TOTAL_WIRE "mats.exec.In.TotalWire.bytes"}: The processed incoming message's
* total wire size - same as the message received logline's {@link #MDC_MATS_IN_SIZE_TOTAL_WIRE
* "mats.in.TotalWire.bytes"} above.
* - {@link #MDC_MATS_EXEC_TIME_USER_LAMBDA "mats.exec.UserLambda.ms"}: Same as for initiation.
* - {@link #MDC_MATS_EXEC_TIME_OUT "mats.exec.Out.ms"}: Same as for initiation.
* - {@link #MDC_MATS_EXEC_QUANTITY_OUT "mats.exec.Out.quantity"}: Same as for initiation.
* - {@link #MDC_MATS_EXEC_SIZE_OUT_TOTAL_WIRE "mats.exec.Out.TotalWire.bytes"}: Same as for initiation.
* - {@link #MDC_MATS_EXEC_TIME_DB_COMMIT "mats.exec.DbCommit.ms"}: Same as for initiation.
* - {@link #MDC_MATS_EXEC_TIME_MSGSYS_COMMIT "mats.exec.MsgSysCommit.ms"}: Same as for initiation
*
* User metrics: Furthermore, any metrics (measurements and timings) set from an initiation or stage will be
* available as separate log lines - same as for initiations.
*
*
* Extra Properties for Endpoint Completed:
When any Stage of an Endpoint (typically the last) either REPLYs,
* or stops the flow (neither sending a REQUEST, NEXT nor GOTO), the endpoint is completed.
*
* - {@link #MDC_MATS_ENDPOINT_COMPLETED "mats.EndpointCompleted"}: Present on the same single logline as Stage
* Completed if this also is the endpoint completion. Notice that the span from an initiation REQUEST to the
* replyTo-reception on a Terminator is also counted as a EndpointCompleted. The value is the time taken from entry on
* the initial stage of an endpoint, to stage completed on the final stage (or from Initiation REQUEST to
* replyTo-reception on the Terminator). This metric is susceptible to time skews between nodes.
*
*
* Extra Properties for Flow Completed:
When a Stage doesn't send any REPLY, REQUEST, NEXT or GOTO, it stops
* the flow. This is the normal situation for a Terminator, but technically a flow may stop anywhere if the stage
* doesn't send a flow message.
*
* - {@link #MDC_MATS_FLOW_COMPLETED "mats.FlowCompleted"}: Present on the same single logline as Stage
* Completed if this also is flow completion. can be used to count mats flows. This count has a very tight
* relationship with {@link #MDC_MATS_INITIATE_COMPLETED}, read above. The value is the time taken from the flow was
* initiated at a {@link MatsInitiator}, or from within a flow, to it ends. This metric is susceptible to time skews
* between nodes.
*
*
*
*
* MDC Properties for Per created Message (both initiations and stage produced messages):
*
* - {@link #MDC_MATS_MESSAGE_SENT "mats.MessageSent"}: Present on single logline per sent message - can be
* used to count sent messages. The value is the same as {@link #MDC_MATS_OUT_TIME_TOTAL "mats.out.Total.ms"}, see
* below.
* - {@link #MDC_MATS_DISPATCH_TYPE "mats.DispatchType"}: The DispatchType enum; INIT, STAGE, STAGE_INIT
* - {@link #MDC_MATS_OUT_MATS_MESSAGE_ID "mats.out.MatsMsgId"}: The messageId the Mats system gave the
* message. Note that it consists of the Mats flow id + an individual part per message in the flow.
* - {@link #MDC_MATS_OUT_MESSAGE_SYSTEM_ID "mats.out.MsgSysId"}: The messageId that the messaging system gave
* the message - for the JMS Implementation, it is the JMSMessageId.
* - NOTICE: NOT using "mats.out.from.app", as that is 'this' App, and thus identical to:
*
"mats.AppName"
.
* - NOTICE: NOT using "mats.out.from.Id", as that is identical to "mats.InitOrStageId".
* - {@link #MDC_INIT_OR_STAGE_ID "mats.InitOrStageId"}: "this" StageId or InitiatorId: Who created this
* message.
* - {@link #MDC_MATS_OUT_TO_ID "mats.out.to.Id"}: target EndpointId/StageId
* - NOTICE: NOT using "mats.out.to.app", since we do not know which app will consume it.
*
* "Flow properties", i.e. common for all received and sent messages in a Mats flow (initiated, received on stage and
* sent from stage):
*
* - {@link #MDC_MATS_INIT_APP "mats.init.App"}: Which App initiated this Mats flow.
* - {@link #MDC_MATS_INIT_ID "mats.init.Id"}: The initiatorId of this MatsFlow;
*
matsInitiate.from(initiatorId)
.
* - {@link #MDC_MATS_AUDIT "mats.Audit"}: Whether this Mats flow should be audited.
* - {@link #MDC_MATS_INTERACTIVE "mats.Interactive"}: Whether this Mats flow should be treated as
* "interactive", meaning that a human is actively waiting for its execution.
* - {@link #MDC_MATS_PERSISTENT "mats.Persistent"}: Whether the messaging system should use persistent (as
* opposed to non-persistent) message passing, i.e. store to disk to survive a crash.
*
* Metrics for message production (note how these compare to the reception of a message, on the "Message
* Received" loglines):
*
* - {@link #MDC_MATS_OUT_TIME_TOTAL "mats.out.Total.ms"}: Total time taken to produce the message, all of the
* Mats envelope ("MatsTrace"), serializing, compressing and producing the message system message (sum of the below
* parts). The same value is present on {@link #MDC_MATS_MESSAGE_SENT "mats.MessageSent"}, see above.
* - {@link #MDC_MATS_OUT_TIME_ENVELOPE_PRODUCE "mats.out.EnvelopeProduce.ms"}: Part of the total time taken to
* produce the Mats envelope ("MatsTrace"), including serialization of all constituents: DTO, STO and any Trace
* Properties.
* - {@link #MDC_MATS_OUT_TIME_ENVELOPE_SERIAL "mats.out.EnvelopeSerial.ms"}: Part of the total time taken to
* serialize the Mats envelope.
* - {@link #MDC_MATS_OUT_SIZE_ENVELOPE_SERIAL "mats.out.EnvelopeSerial.bytes"}: Size of the serialized Mats
* envelope.
* - {@link #MDC_MATS_OUT_TIME_ENVELOPE_COMPRESS "mats.out.EnvelopeCompress.ms"}: Part of the total time taken
* to compress the serialized Mats envelope
* - {@link #MDC_MATS_OUT_SIZE_ENVELOPE_WIRE "mats.out.EnvelopeWire.bytes"}: Size of the compressed serialized
* Mats envelope.
* - {@link #MDC_MATS_OUT_TIME_MSGSYS "mats.out.MsgSys.ms"}: Part of the total time taken to produce and send
* the message system message.
* - {@link #MDC_MATS_OUT_SIZE_TOTAL_WIRE "mats.out.TotalWire.bytes"}: Best approximation of the total message
* system wire size (envelope + sideloads + any meta info set on the message). The overhead of the message system itself
* will probably not be included.
* - {@link #MDC_MATS_OUT_SIZE_DATA_SERIAL "mats.out.DataSerial.bytes"}: The serialized size of the outgoing
* data object (DTO). Note that
null
might not return 0
bytes, depending on how the serializer
* works. If the serializer employs Strings, the returned value is String.length()
, which might not exactly
* be the number of bytes depending on the String contents.
* - NOTICE: NOT using
"mats.out.StateSerial.bytes"
, as it felt confusing what any outgoing state
* relates to: It is not the target/receiving stage, as specified in {@link #MDC_MATS_OUT_TO_ID}. The outgoing state (if
* any) is a part of the stack, and relates to the next stage of the current endpoint. However, this metric is available
* on the incoming side, via {@link #MDC_MATS_IN_SIZE_STATE_SERIAL}.
*
*
* Note: Both Initiation and Stage completed can produce messages. For the very common case where this is just a
* single message (a single initiated message starting a Mats flow, or a REQUEST or REPLY message from a Stage (in a
* flow)), the "Per message" log line is combined with the Initiation or Stage Completed log line - on the message part,
* the two lines are separated by a "/n", while each of the properties that come with the respective log line either are
* common - i.e. they have the same name, and would have the same value - or they are differently named, so that the
* combination does not imply any "overwrite" of a property name. If you search for a property that only occurs on "Per
* message" log lines (you e.g. want to count, or select, all outgoing message), you will get hits for log lines that
* are in the "combined" form (e.g. initiation producing a single message), and for log lines for individual messages
* (i.e. where an initiation produced two messages, which results in three log lines: 1 for the initiation, and 2 for
* the messages).
*
* Note: This logging interceptor honors the {@link MatsInitiate#setTraceProperty(String, Object) Trace Property}
* {@link MatsLoggingInterceptor#SUPPRESS_LOGGING_TRACE_PROPERTY_KEY} and the Endpoint Attribute
* {@link MatsLoggingInterceptor#SUPPRESS_LOGGING_ENDPOINT_ALLOWS_ATTRIBUTE_KEY}. Number of suppressed log outputs per
* matsFactoryName/initAppName/initiatorId/stageId will be logged every 30 minutes (stageId = "INIT" for initiations),
* on the {@link #LOG_STAGE_NAME} logger. The first logging for each combo after logging the number of suppressions will
* also be logged in full.
*
* Note: This interceptor (SLF4J Logger with Metrics on MDC) has special support in JmsMatsFactory
: If
* present on the classpath, it is automatically installed using the {@link #install(MatsFactory)} install
* method. This interceptor implements the special marker-interface {@link MatsLoggingInterceptor} of which there
* can only be one instance installed in a JmsMatsFactory
- implying that if you want a different type of
* logging, you may implement a custom variant (either subclassing this, on your own risk, or start from scratch), and
* simply install it, leading to this instance being removed (or just not have this variant on the classpath).
*
* @author Endre Stølsvik - 2021-02-07 12:45 - http://endre.stolsvik.com
*/
public class MatsMetricsLoggingInterceptor
implements MatsLoggingInterceptor, MatsInitiateInterceptor, MatsStageInterceptor {
public static final String LOG_INIT_NAME = "io.mats3.log.init";
public static final String LOG_STAGE_NAME = "io.mats3.log.stage";
public static final int NUMBER_OF_MILLIS_BETWEEN_SUPPRESSION_LOG = 30 * 60 * 1000;
protected static final Logger log_init = LoggerFactory.getLogger(LOG_INIT_NAME);
protected static final Logger log_stage = LoggerFactory.getLogger(LOG_STAGE_NAME);
protected static final String SUPPRESS_LOGGING_INTERCEPT_CONTEXT_ATTRIBUTE = "suppressStageLogging";
public static final String LOG_PREFIX = "#MATSLOG# ";
public static final String SUPPRESSION_MDC_KEY_PARTS_SEPARATOR = ",";
// Not using "mats." prefix for "traceId", as it is hopefully generic yet specific
// enough that it might be used in similar applications.
public static final String MDC_TRACE_ID = "traceId";
public static final MatsMetricsLoggingInterceptor INSTANCE = new MatsMetricsLoggingInterceptor();
// !!! Note that these MDCs are already set by JmsMats core: !!!
// String MDC_MATS_APP_NAME = "mats.AppName";
// String MDC_MATS_APP_VERSION = "mats.AppVersion";
// ============================================================================================================
// ===== COMMON for Initiate and Stage Completed, with timings:
public static final String MDC_MATS_VERSION = "mats.Version";
public static final String MDC_INIT_OR_STAGE_ID = "mats.InitOrStageId";
// ... Metrics:
public static final String MDC_MATS_EXEC_TIME_TOTAL = "mats.exec.Total.ms";
public static final String MDC_MATS_EXEC_TIME_USER_LAMBDA = "mats.exec.UserLambda.ms";
public static final String MDC_MATS_EXEC_TIME_OUT = "mats.exec.Out.ms";
public static final String MDC_MATS_EXEC_QUANTITY_OUT = "mats.exec.Out.quantity";
public static final String MDC_MATS_EXEC_SIZE_OUT_TOTAL_WIRE = "mats.exec.Out.TotalWire.bytes";
public static final String MDC_MATS_EXEC_TIME_DB_COMMIT = "mats.exec.DbCommit.ms";
public static final String MDC_MATS_EXEC_TIME_MSGSYS_COMMIT = "mats.exec.MsgSysCommit.ms";
public static final String MDC_MATS_EXEC_OPS_TIMING_PREFIX = "mats.exec.ops.time.";
public static final String MDC_MATS_EXEC_OPS_MEASURE_PREFIX = "mats.exec.ops.measure.";
public static final String MDC_MATS_EXEC_OPS_KEYNAME = "mats.exec.ops.key";
public static final String MDC_MATS_EXEC_OPS_DESCRIPTION = "mats.exec.ops.description";
// ============================================================================================================
// ===== For Initiate Completed (..in addition to "COMMON init/stage" above):
// !!! Note that these MDCs are already set by JmsMats core: !!!
// MDC_MATS_INIT = "mats.Init"; // 'true' on any loglines involving Initialization (also within Stages)
// MDC_MATS_CALL_NO = "mats.CallNo" // The Call Number, at 0 for initiations.
// MDC_TRACE_ID = "traceId" // Set as soon as the user code sets it on the initialization.
// Set on a single logline per completed initiation, value is the number of millis taken
// (same value as MDC_MATS_EXEC_TIME_TOTAL)
public static final String MDC_MATS_INITIATE_COMPLETED = "mats.InitiateCompleted";
// ============================================================================================================
// ===== For Receiving a message
// !!! Note that these MDCs are already set by JmsMats core: !!!
// MDC_MATS_STAGE = "mats.Stage"; // 'true' on Stage Processor threads (set fixed on the consumer thread)
// MDC_MATS_STAGE_ID = "mats.StageId";
// MDC_MATS_IN_MESSAGE_SYSTEM_ID = "mats.in.MsgSysId";
// MDC_MATS_CALL_NO = "mats.CallNo" // The Call Number, first stage of flow is 1, then increases with calls.
// MDC_TRACE_ID = "traceId"
// 'true' on a single logline per received message:
public static final String MDC_MATS_MESSAGE_RECEIVED = "mats.MessageReceived";
public static final String MDC_MATS_IN_FROM_APP_NAME = "mats.in.from.App";
public static final String MDC_MATS_IN_FROM_ID = "mats.in.from.Id";
// NOTICE: NOT using MDC_MATS_IN_TO_APP, as that is identical to MDC_MATS_APP_NAME
// NOTICE: NOT using MDC_MATS_IN_TO_ID, as that is identical to MDC_MATS_STAGE_ID
public static final String MDC_MATS_IN_MATS_MESSAGE_ID = "mats.in.MatsMsgId";
public static final String MDC_MATS_IN_MESSAGE_TYPE = "mats.in.MessageType";
// ... Metrics:
// Notice that metric this is susceptible to time skews between nodes.
public static final String MDC_MATS_IN_TIME_SINCE_SENT = "mats.in.SinceSent.ms";
// Notice that metric this is susceptible to time skews between nodes.
public static final String MDC_MATS_IN_TIME_SINCE_PRECEDING_ENDPOINT_STAGE = "mats.in.PrecedEpStage.ms";
public static final String MDC_MATS_IN_SIZE_TOTAL_WIRE = "mats.in.TotalWire.bytes";
public static final String MDC_MATS_IN_TIME_TOTAL_PREPROC_AND_DESERIAL = "mats.in.TotalPreprocDeserial.ms";
public static final String MDC_MATS_IN_TIME_MSGSYS_DECONSTRUCT = "mats.in.MsgSysDeconstruct.ms";
public static final String MDC_MATS_IN_SIZE_ENVELOPE_WIRE = "mats.in.EnvelopeWire.bytes";
public static final String MDC_MATS_IN_TIME_ENVELOPE_DECOMPRESS = "mats.in.EnvelopeDecompress.ms";
public static final String MDC_MATS_IN_SIZE_ENVELOPE_SERIAL = "mats.in.EnvelopeSerial.bytes";
public static final String MDC_MATS_IN_TIME_ENVELOPE_DESERIAL = "mats.in.EnvelopeDeserial.ms";
public static final String MDC_MATS_IN_TIME_DATA_AND_STATE_DESERIAL = "mats.in.DataAndStateDeserial.ms";
public static final String MDC_MATS_IN_SIZE_STATE_SERIAL = "mats.in.StateSerial.bytes";
public static final String MDC_MATS_IN_SIZE_DATA_SERIAL = "mats.in.DataSerial.bytes";
// ============================================================================================================
// ===== For Stage Completed (..in addition to "COMMON init/stage" above):
// !!! Note that these MDCs are already set by JmsMats core: !!!
// MDC_TRACE_ID = "traceId"
// MDC_MATS_STAGE = "mats.Stage"; // 'true' on Stage Processor threads (set fixed on the consumer thread)
// MDC_MATS_STAGE_ID = "mats.StageId"; // set fixed on the consumer thread
// MDC_MATS_STAGE_INDEX = "mats.StageIndex" // set fixed on the consumer thread
// MDC_MATS_IN_MESSAGE_SYSTEM_ID = "mats.in.MsgSysId";
// MDC_MATS_CALL_NUMBER = "mats.CallNo" // The Call Number, init=0, first_stage=1, then increases with calls.
// Set on a single logline per completed stage, value is the number of millis taken
// (same value as MDC_MATS_EXEC_TIME_TOTAL)
public static final String MDC_MATS_STAGE_COMPLETED = "mats.StageCompleted";
// Set on a single logline per completed
public static final String MDC_MATS_PROCESS_RESULT = "mats.ProcessResult";
// ..... specific Stage complete metric - along with the other ".exec." from the COMMON Init/Stage Completed
// Note that this is the same timing as the MDC_MATS_IN_TIME_TOTAL_PREPROC_AND_DESERIAL
public static final String MDC_MATS_EXEC_TIME_TOTAL_PREPROC_AND_DESERIAL = "mats.exec.TotalPreprocDeserial.ms";
// Note that this is the same size as the MDC_MATS_IN_SIZE_TOTAL_WIRE
public static final String MDC_MATS_EXEC_SIZE_IN_TOTAL_WIRE = "mats.exec.In.TotalWire.bytes";
// ============================================================================================================
// ===== For Endpoint Completed - i.e. a stage of ep that either REPLY or stop the flow (no REQ,NEXT,GOTO)
// !! NOTE: These are /in addition/ to the Stage Completed above, e.g. MDC_MATS_EXEC_PROCESS_RESULT
// !! NOTE: This will also be set on a Terminator, but that will only be for the Terminator endpoint itself, not
// for initiator-to-terminator timings.
// Set on a single logline per completed endpoint - it will be on the Stage Completed log lines.
// value is the number of millis taken.
public static final String MDC_MATS_ENDPOINT_COMPLETED = "mats.EndpointCompleted";
// ..... specific Endpoint Completed metric. Notice that metric this is susceptible to time skews between nodes.
// DEPRECATED - use the above. TODO: Remove in 2024.
public static final String MDC_MATS_ENDPOINT_COMPLETE_TIME_TOTAL = "mats.endpoint.Total.ms";
// ============================================================================================================
// ===== For Flow Completed - i.e. a stage in a flow that does not send any outgoing flow msg (REPLY,REQ,NEXT,GOTO)
// ===== (Typically at a Terminator, i.e. endpoint that have void as outgoing message)
// !! NOTE: These are /in addition/ to the Stage Completed above, e.g. MDC_MATS_EXEC_PROCESS_RESULT
// Set on a single logline per completed mats flow - it will be on the Stage Completed log lines.
// value is the number of millis taken.
public static final String MDC_MATS_FLOW_COMPLETED = "mats.FlowCompleted";
// ..... specific Flow Completed metric. Notice that this metric is susceptible to time skews between nodes.
// DEPRECATED - use the above. TODO: Remove in 2024.
public static final String MDC_MATS_FLOW_COMPLETE_TIME_TOTAL = "mats.flow.Total.ms";
// ============================================================================================================
// ===== For Sending a single message (from init, or stage) - one line per message
// Note the point about "combined" loglines for stage-complete + output-single-message, read JavaDoc.
// 'true' on single logline per msg
public static final String MDC_MATS_MESSAGE_SENT = "mats.MessageSent";
// Set on single logline per msg: INIT, STAGE, STAGE_INIT
public static final String MDC_MATS_DISPATCH_TYPE = "mats.DispatchType";
public static final String MDC_MATS_OUT_MATS_MESSAGE_ID = "mats.out.MatsMsgId";
public static final String MDC_MATS_OUT_MESSAGE_SYSTEM_ID = "mats.out.MsgSysId";
// NOTICE: NOT using MDC_MATS_OUT_FROM_APP / "mats.out.from.App", as that is 'this' App: MDC_MATS_APP_NAME.
// NOTICE: MDC_INIT_OR_STAGE_ID is also set when sending messages (as well as on init or stage complete).
// NOTICE: Currently, MDC_INIT_OR_STAGE_ID == MDC_MATS_OUT_FROM_ID - will be removed
// TODO: REMOVE once all are >=0.18.8, only leave "mats.InitOrStageId" instead.
public static final String MDC_MATS_OUT_FROM_ID = "mats.out.from.Id"; // "this" StageId/InitiatorId.
public static final String MDC_MATS_OUT_TO_ID = "mats.out.to.Id"; // target StageId.
// NOTICE: NOT using MDC_MATS_OUT_TO_APP, since we do not know which app will consume it.
// ... Metrics:
public static final String MDC_MATS_OUT_TIME_TOTAL = "mats.out.Total.ms";
public static final String MDC_MATS_OUT_SIZE_DATA_SERIAL = "mats.out.DataSerial.bytes";
public static final String MDC_MATS_OUT_TIME_ENVELOPE_PRODUCE = "mats.out.EnvelopeProduce.ms";
public static final String MDC_MATS_OUT_TIME_ENVELOPE_SERIAL = "mats.out.EnvelopeSerial.ms";
public static final String MDC_MATS_OUT_SIZE_ENVELOPE_SERIAL = "mats.out.EnvelopeSerial.bytes";
public static final String MDC_MATS_OUT_TIME_ENVELOPE_COMPRESS = "mats.out.EnvelopeCompress.ms";
public static final String MDC_MATS_OUT_SIZE_ENVELOPE_WIRE = "mats.out.EnvelopeWire.bytes";
public static final String MDC_MATS_OUT_SIZE_TOTAL_WIRE = "mats.out.TotalWire.bytes";
public static final String MDC_MATS_OUT_TIME_MSGSYS = "mats.out.MsgSys.ms";
// ============================================================================================================
// ===== For both Message Received and Message Send (note: part of MatsTrace itself: Common for all msgs in flow)
public static final String MDC_MATS_INIT_APP = "mats.init.App";
public static final String MDC_MATS_INIT_ID = "mats.init.Id"; // matsInitiate.from(initiatorId).
public static final String MDC_MATS_AUDIT = "mats.Audit";
public static final String MDC_MATS_INTERACTIVE = "mats.Interactive";
public static final String MDC_MATS_PERSISTENT = "mats.Persistent";
/**
* Install the singleton {@link #INSTANCE} as MatsFactory plugin.
*
* @param matsFactory
* the {@link MatsFactory} MatsFactory to install it on.
*/
public static void install(MatsFactory matsFactory) {
matsFactory.getFactoryConfig().installPlugin(INSTANCE);
}
/**
* Removes the singleton {@link #INSTANCE} as MatsFactory plugin.
*
* @param matsFactory
* the {@link MatsFactory} to remove it from.
*/
public static void remove(MatsFactory matsFactory) {
matsFactory.getFactoryConfig().removePlugin(INSTANCE);
}
protected final ConcurrentHashMap _suppressions = new ConcurrentHashMap<>();
protected MatsMetricsLoggingInterceptor() {
Thread suppressionSummarizerThread = new Thread(this::suppressionSummarizerThreadRunnable,
LOG_PREFIX + " Periodic Log Suppression summarizer");
suppressionSummarizerThread.setDaemon(true); // Must die with JVM, we do not have stop-ability
suppressionSummarizerThread.start();
}
protected void suppressionSummarizerThreadRunnable() {
while (true) {
try {
Thread.sleep(NUMBER_OF_MILLIS_BETWEEN_SUPPRESSION_LOG);
}
catch (InterruptedException e) {
log_stage.info(LOG_PREFIX + "Surprisingly, we were interrupted. Exiting.");
break;
}
int suppressionSize = _suppressions.size();
// ?: Is the number of suppressions very high?
if (suppressionSize > 500) {
// -> Yes, very high number of suppressions - this is most probably a bug which must be addressed.
log_stage.error(LOG_PREFIX + "NOTE!! THERE ARE WAY TOO MANY [" + suppressionSize
+ "] SUPPRESSION ENTRIES!"
+ " This indicates that fromId (initiatorId) on initiations is set dynamically. You must fix"
+ " this, or risk out of memory situations.");
}
// ?: Did we have any suppressions?
if (suppressionSize == 0) {
// -> No, so exit.
continue;
}
// :: Copy out the Map, and clear it while doing so.
Map suppressionsCopy = new HashMap<>();
Iterator> it = _suppressions.entrySet().iterator();
while (it.hasNext()) {
Entry entry = it.next();
it.remove();
suppressionsCopy.put(entry.getKey(), entry.getValue().get());
}
// :: Output single logline, with all the counts as separate MDC entries.
try {
int totalSuppressed = 0;
for (Entry entry : suppressionsCopy.entrySet()) {
int count = entry.getValue();
totalSuppressed += count;
MDC.put(entry.getKey(), Integer.toString(entry.getValue()));
}
log_stage.info(LOG_PREFIX + " Suppressed log entries! Total: [" + totalSuppressed + "], over ["
+ suppressionsCopy.size() + "] groups. Group counts are in MDC.");
}
finally {
MDC.clear();
}
}
}
private boolean shouldSuppressInitLogging(List outgoingMessages) {
// It is the message that carries the information about suppression (it is a trace property), thus we need to
// inspect the initiated outgoing message(s). If there are none, we'll have to conclude that we shall log.
// ?: No outgoing messages?
if (outgoingMessages.isEmpty()) {
// -> No outgoing messages, so we shall NOT suppress.
return false;
}
// Might be multiple messages: All messages must say suppress to actually do suppression.
for (MatsSentOutgoingMessage outgoingMessage : outgoingMessages) {
Object suppressed = outgoingMessage.getTraceProperty(SUPPRESS_LOGGING_TRACE_PROPERTY_KEY, Object.class);
// ?: Does this message NOT want suppression?
if (suppressed != Boolean.TRUE) {
// -> This message DOES NOT want suppression, so we shall not suppress.
return false;
}
}
// All messages wanted suppression.
return true;
}
@Override
public void initiateCompleted(InitiateCompletedContext ctx) {
List outgoingMessages = ctx.getOutgoingMessages();
// :: Handle log suppression
// ?: Does this initiation want log suppression?
FactoryConfig factoryConfig = ctx.getInitiator().getParentFactory().getFactoryConfig();
if (shouldSuppressInitLogging(outgoingMessages)) {
// -> Yes, this initiation want log suppression.
// Find initiatorId, pick first message.
// Suppression of init can only happen if there are outgoing messages, so there is at least one.
MatsSentOutgoingMessage outgoingMessage = outgoingMessages.get(0);
String suppressedId = "logsuppress:"
+ factoryConfig.getName()
+ SUPPRESSION_MDC_KEY_PARTS_SEPARATOR
+ outgoingMessage.getInitiatingAppName()
+ SUPPRESSION_MDC_KEY_PARTS_SEPARATOR
+ outgoingMessage.getInitiatorId()
+ SUPPRESSION_MDC_KEY_PARTS_SEPARATOR
+ "INIT"; // This is an initiation.
// :: The first suppression (after summarize-suppressions-and-clear) should not be suppressed!
// Increase the counter, but start from -1
int numberOfSuppressions = _suppressions.computeIfAbsent(suppressedId, s -> new AtomicInteger(-1))
.incrementAndGet();
// ?: Was this the first logging after summarize-suppressions-and-clear.
if (numberOfSuppressions > 0) {
// -> No, not first logging, so do actual suppression - return.
return;
}
// E-> Yes, this was first suppression, so do not suppress!
}
// :: First output the user measurements
outputMeasurementsLoglines(log_init, ctx);
// :: Then the "completed" logline, either combined with a single message - or multiple lines for multiple msgs
Map completedMDC = Collections.singletonMap(MDC_MATS_INITIATE_COMPLETED,
msS(ctx.getTotalExecutionNanos()));
String messageSenderName = factoryConfig.getName() + "|" + ctx.getInitiator().getName();
String matsVersion = factoryConfig.getMatsImplementationVersion();
/*
* The initiatorId is set by sending an actual message. If no message is sent from an initiation lambda, there
* is no initiatorId. But the lambda still executed, so we want to measure it - thus using a fictive initiatorId
* in the no-outgoing-message case.
*
* In case of multiple messages in one initiation, each "initiatorId" (i.e. MatsInitiate.from(..)) might be
* different. Assuming that I have an idea of how developers use the system, this should really not be a common
* situation. Therefore, we just pick the first message's "from" (i.e. "initiatorId") to tag the timings with.
*
* The same holds for the interactive flag.
*/
String firstMsgInitiatorId;
boolean interactive;
// ?: Do we have outgoing messages?
if (outgoingMessages.isEmpty()) {
// -> No outgoing messages, using dummy "initiatorId", and set interactive to false.
firstMsgInitiatorId = MatsMetricsInterceptor.INITIATOR_ID_WHEN_NO_OUTGOING_MESSAGES;
interactive = false;
}
else {
// -> Yes, there are outgoing messages. Use first message's initiatorId and interactive flag.
MatsSentOutgoingMessage firstMessage = outgoingMessages.get(0);
firstMsgInitiatorId = firstMessage.getInitiatorId();
interactive = firstMessage.isInteractive();
}
String initiatingAppName = factoryConfig.getAppName();
commonStageAndInitiateCompleted(ctx, matsVersion, firstMsgInitiatorId, initiatingAppName, firstMsgInitiatorId,
interactive, ctx.getInitiateProcessResult(), log_init, outgoingMessages,
messageSenderName, "", 0L, completedMDC);
}
protected boolean shouldSuppressStageLogging(StageCommonContext ctx) {
// ?: Does the Endpoint allow for log suppression?
Object suppressionAllowed = ctx.getStage().getParentEndpoint().getEndpointConfig()
.getAttribute(SUPPRESS_LOGGING_ENDPOINT_ALLOWS_ATTRIBUTE_KEY);
if (suppressionAllowed == Boolean.TRUE) {
// -> Yes, endpoint allows for log suppression.
// ?: Does this Mats Flow want log suppression?
Object suppressed = ctx.getProcessContext()
.getTraceProperty(SUPPRESS_LOGGING_TRACE_PROPERTY_KEY, Object.class);
// Return whether this Stage wants log suppression
return suppressed == Boolean.TRUE; // Will be 'null' if not.
}
return false;
}
@Override
public void stageReceived(StageReceivedContext ctx) {
ProcessContext