Skip to content

Observability Specification

Defines the Functorium framework’s Observability field/tag specification, Meter definition rules, and message template patterns. For Pipeline execution order, OpenTelemetryOptions settings, and custom extension points, see the Pipeline Specification.

ConceptDescription
Service Attributesservice.namespace, service.name, etc. for OpenTelemetry standard service identification
Error Classificationexpected (business error), exceptional (system error), aggregate (composite error)
3-Pillar Field/Tagrequest.*, response.*, error.* fields used identically across Logging, Metrics, and Tracing
Meter Name{service.namespace}.{layer}[.{category}] pattern
Instrumentrequests (Counter), responses (Counter), duration (Histogram)
Message TemplateStructured log message format per layer and Event ID scheme (Application 1001-1004, Adapter 2001-2004)
Span Name{layer} {category}[.{type}] {handler}.{method}
ctx.* Context FieldsSource Generator automatically converts Request/Response/DomainEvent properties to ctx.{snake_case} fields. Pillar targeting via [CtxTarget] (default: Logging + Tracing, Metrics is opt-in)

Functorium uses OpenTelemetry Service Attributes for service identification.

AttributeDescriptionExample
service.namespaceNamespace of service.name. Helps distinguish service groups (e.g., by team or environment).mycompany.production
service.nameLogical name of the service. Must be identical across all horizontally scaled instances.orderservice
service.versionVersion string of the service API or implementation.2.0.0
service.instance.idUnique ID of the service instance. Must be globally unique per service.namespace,service.name pair. Uses HOSTNAME environment variable when available, otherwise falls back to Environment.MachineName.my-pod-abc123 (Kubernetes), DESKTOP-ABC123 (Windows)
deployment.environmentAttribute identifying the deployment environment.production, staging

Recommended: Use lowercase values for service.name and service.namespace (e.g., mycompany.production, orderservice). This ensures consistency with OpenTelemetry conventions and prevents case-sensitivity issues in downstream systems (dashboards, queries, alerts).

The following table summarizes how error.type and error.code tag values are determined based on the error cause.

Error Caseerror.typeerror.codeDescription
IHasErrorCode + IsExpected"expected"Error codeExpected business logic error with error code
IHasErrorCode + IsExceptional"exceptional"Error codeExceptional system error with error code
ManyErrors"aggregate"Primary error codeMultiple errors aggregated (Exceptional takes priority)
Expected (LanguageExt)"expected"Type nameLanguageExt default expected error without error code
Exceptional (LanguageExt)"exceptional"Type nameLanguageExt default exceptional error without error code

error.type and @error.ErrorType use different value formats for different purposes.

Error Typeerror.type (for filtering)@error.ErrorType (for detail)
Expected Error"expected""ErrorCodeExpected"
Exceptional Error"exceptional""ErrorCodeExceptional"
Aggregate Error"aggregate""ManyErrors"
LanguageExt Expected"expected""Expected"
LanguageExt Exceptional"exceptional""Exceptional"
  • error.type: Standardized value for log filtering/querying (consistent with Metrics/Tracing)
  • @error.ErrorType: Actual class name for detailed error type identification

Uses snake_case + dot notation in compliance with OpenTelemetry semantic conventions.

# Correct examples
request.layer
request.category.type
request.handler.method
response.status
error.code
# Incorrect examples
requestLayer # camelCase not allowed
request-layer # kebab-case not allowed
REQUEST_LAYER # UPPER_SNAKE_CASE not allowed

Fields are organized in a hierarchical structure of namespace and property.

NamespaceDescriptionExample
request.*Request-related informationrequest.layer, request.handler.name
response.*Response-related informationresponse.status, response.elapsed
error.*Error-related informationerror.type, error.code
CategoryRuleExample
Static fields (event-related).countrequest.event.count
Dynamic fields (parameter size)_countrequest.params.orders_count
Adjective/noun combination_countresponse.event.success_count, response.event.failure_count
# Correct examples
request.event.count # Static .count
response.event.success_count # Adjective combination _count
request.params.orders_count # Dynamic parameter _count
# Incorrect examples
response.event.success.count # Do not use .count for combination count
request.params.orders.count # Do not use .count for dynamic fields

Application Layer: (Unit tests: Logging, Metrics, Tracing)

Field/TagLoggingMetricsTracingDescription
request.layerArchitecture layer ("application")
request.category.nameRequest category ("usecase")
request.category.typeCQRS type ("command", "query")
request.handler.nameHandler class name
request.handler.methodHandler method name ("Handle")
response.statusResponse status ("success", "failure")
response.elapsed-*Elapsed time (seconds)
error.typeError classification ("expected", "exceptional", "aggregate")
error.codeDomain-specific error code
@error--Structured error object (detailed)

Adapter Layer: (Unit tests: Logging, Metrics, Tracing)

Field/TagLoggingMetricsTracingDescription
request.layerArchitecture layer ("adapter")
request.category.nameCategory (e.g., "repository")
request.handler.nameHandler class name
request.handler.methodHandler method name
response.statusResponse status ("success", "failure")
response.elapsed-*Elapsed time (seconds)
error.typeError classification ("expected", "exceptional", "aggregate")
error.codeDomain-specific error code
@error--Structured error object (detailed)

* Why response.elapsed is not a Metrics tag:

  • Metrics uses a dedicated duration Histogram instrument to capture processing time, which is the OpenTelemetry recommended approach for latency measurement.
  • Using elapsed time as a tag causes high cardinality explosion (each unique duration value creates a new time series, degrading metric storage and query performance).
  • Histogram provides statistical aggregation (percentiles, averages, counts) that is more useful for monitoring than individual elapsed values.

(Unit tests: Logging, Metrics, Tracing)

DomainEvent Publisher is classified as the Adapter layer, with request.layer as "adapter" and request.category.name as "event".

Field/TagLoggingMetricsTracingDescription
request.layerArchitecture layer ("adapter")
request.category.nameRequest category ("event")
request.handler.nameEvent type name or Aggregate type name
request.handler.methodMethod name ("Publish", "PublishTrackedEvents")
request.aggregate.count--Number of Aggregate types (PublishTrackedEvents only)
request.event.count-Number of events in batch publishing (Aggregate only)
response.statusResponse status ("success", "failure")
response.elapsed-*Elapsed time (seconds)
response.event.success_count-Number of successful events on partial failure (Partial Failure only)
response.event.failure_count-Number of failed events on partial failure (Partial Failure only)
error.typeError classification ("expected", "exceptional")
error.codeDomain-specific error code
@error--Structured error object (detailed)

(Unit tests: Logging, Metrics, Tracing)

DomainEventHandler is classified as the Application layer, with request.layer as "application", request.category.name as "usecase", and request.category.type as "event".

Field/TagLoggingMetricsTracingDescription
request.layerArchitecture layer ("application")
request.category.nameRequest category ("usecase")
request.category.typeCQRS type ("event")
request.handler.nameHandler class name
request.handler.methodMethod name ("Handle")
request.event.type-Event type name
request.event.id-Event unique ID
@request.message--Event object (on request)
response.statusResponse status ("success", "failure")
response.elapsed-*-Elapsed time (seconds)
error.typeError classification ("expected", "exceptional")
error.codeDomain-specific error code

Note: DomainEventHandler’s response.elapsed is not set on Tracing Span tags (Logging only). Since Spans inherently have their own start/end times (duration), a separate elapsed field would be redundant. DomainEventHandler’s ErrorResponse logs the Exception object directly (instead of @error). DomainEventHandler returns ValueTask, so @response.message is not recorded.


ctx.* User-Defined Context Fields (3-Pillar)

Section titled “ctx.* User-Defined Context Fields (3-Pillar)”

ctx.* fields are user-defined context fields that simultaneously propagate business context to Logging, Tracing, and Metrics. Source Generator automatically detects public properties of Request/Response/DomainEvent and generates IUsecaseCtxEnricher<TRequest, TResponse> or IDomainEventCtxEnricher<TEvent> implementations. CtxEnricherPipeline runs as the first Pipeline, making ctx.* data accessible to subsequent Metrics/Tracing/Logging Pipelines.

ItemDescription
Target PillarLogging + Tracing (default). Metrics requires explicit opt-in via [CtxTarget]
Generator (Usecase)CtxEnricherGenerator — detects records implementing ICommandRequest<T> / IQueryRequest<T>
Generator (DomainEvent)DomainEventCtxEnricherGenerator — detects T from classes implementing IDomainEventHandler<T>
Runtime MechanismCtxEnricherContext.Push(name, value, pillars) — simultaneous propagation to Logging/Tracing/Metrics
Target PropertiesPublic scalar and collection properties (complex types are excluded)
Pipeline OrderCtxEnricher → Metrics → Tracing → Logging → Validation → ... → Handler
PillarMechanismDescription
LoggingSerilog LogContext.PushPropertyOutput as structured log field
TracingActivity.Current?.SetTagOutput as Span Attribute
MetricsTagMetricsTagContext (AsyncLocal) → TagList mergeAdded as dimension to existing Counter/Histogram
MetricsValueRecord value to separate Histogram instrumentRecord numeric fields as targets for statistical aggregation
[Flags]
public enum CtxPillar
{
Logging = 1, // Serilog LogContext
Tracing = 2, // Activity.SetTag
MetricsTag = 4, // TagList dimension (low cardinality only)
MetricsValue = 8, // Histogram value recording (numeric only)
Default = Logging | Tracing, // Default value
All = Logging | Tracing | MetricsTag,
}
ScopePatternExample (C# to ctx field)
Usecase Requestctx.{containing_type}.request.{property}PlaceOrderCommand.Request.CustomerIdctx.place_order_command.request.customer_id
Usecase Responsectx.{containing_type}.response.{property}PlaceOrderCommand.Response.OrderIdctx.place_order_command.response.order_id
DomainEvent (top-level)ctx.{event}.{property}OrderPlacedEvent.CustomerIdctx.order_placed_event.customer_id
DomainEvent (nested)ctx.{containing_type}.{event}.{property}Order.CreatedEvent.OrderIdctx.order.created_event.order_id
Interface scopectx.{interface (I removed)}.{property}IRegional.RegionCodectx.regional.region_code
[CtxRoot] promotionctx.{property}[CtxRoot] CustomerIdctx.customer_id
Collectionabove rules}_count suffixList<OrderLine> Linesctx.place_order_command.request.lines_count

All names are converted from PascalCase to snake_case. The I prefix is removed from interface names (ICustomerRequestcustomer_request, IXx). Field names are identical across all Pillars — the same ctx.* names are used in Logging, Tracing, and Metrics.

[CtxRoot]
[CtxTarget(CtxPillar.All)] // All properties -> 3-Pillar Tag
public interface IRegional
{
string RegionCode { get; } // → ctx.region_code (Root + MetricsTag)
}
public sealed class PlaceOrderCommand
{
public sealed record Request(
string CustomerId, // Default (L+T). High cardinality
[CtxTarget(CtxPillar.All)] bool IsExpress, // 3-Pillar Tag. Boolean safe
[CtxTarget(CtxPillar.Default | CtxPillar.MetricsValue)]
int ItemCount, // L+T + Histogram recording
List<OrderLine> Lines, // Default (L+T). Count propagation
[CtxTarget(CtxPillar.Logging)] string InternalNote, // Logging only
[CtxIgnore] string DebugInfo // Fully excluded
) : ICommandRequest<Response>, IRegional;
public sealed record Response(
string OrderId,
[CtxTarget(CtxPillar.Default | CtxPillar.MetricsValue)]
decimal TotalAmount // L+T + Histogram recording
);
}

✅ = Supported, - = Not supported/Not applicable

ctx FieldTypeLoggingTracingMetrics TagMetrics Value
ctx.region_codekeyword-
ctx.place_order_command.request.customer_idkeyword--
ctx.place_order_command.request.is_expressboolean-
ctx.place_order_command.request.item_countlong-
ctx.place_order_command.request.lines_countlong--
ctx.place_order_command.request.internal_notekeyword---
ctx.place_order_command.response.order_idkeyword--
ctx.place_order_command.response.total_amountdouble-
C# TypeOpenSearch Type GroupNotes
boolboolean
byte, sbyte, short, ushort, int, uint, long, ulonglong
float, double, decimaldouble
stringkeyword
Guid, DateTime, DateTimeOffset, TimeSpan, DateOnly, TimeOnly, Urikeyword
enumkeyword
Option<T> (LanguageExt)keyword
IValueObject, IEntityId<T> implementationskeyword.ToString() call (DomainEvent only)
Nullable<T>Delegates to inner T
Collection (List<T>, IReadOnlyList<T>, etc.)longElement count value
Other complex types (class, record, struct)— (excluded)Only scalar/Collection types are targeted

If different type groups are assigned to the same ctx field name, OpenSearch dynamic mapping conflicts occur (compile-time diagnostic FUNCTORIUM002).

AttributeTargetEffect
[CtxRoot]interface, property, parameterPromote field to ctx.{field} root level. Independent of Pillar targeting (affects naming only)
[CtxIgnore]class, property, parameterExcluded from all Pillars. Takes priority over [CtxTarget]
[CtxTarget(CtxPillar)]interface, property, parameterSpecify Pillar target. Defaults to Default (Logging + Tracing) when unspecified
[CtxIgnore]? → YES → Excluded from all Pillars
↓ NO
[CtxTarget] specified? → YES → Specified Pillar
↓ NO
Default → CtxPillar.Default (Logging + Tracing)

Property/parameter level settings always take priority over interface level settings.

Cardinality LevelApplicable TypesMetricsTagMetricsValue
FixedboolSafeNot allowed
BoundedLowenumConditionalNot allowed
Unboundedstring, Guid, DateTime, IValueObject, IEntityId<T>, Option<T>Warning (FUNCTORIUM005)Not allowed
Numericint, long, decimal, doubleWarning (FUNCTORIUM005)Allowed
NumericCountCollection countWarning (FUNCTORIUM005)Allowed

DomainEvent Enricher automatically excludes the default properties of the IDomainEvent interface. These properties are already output as standard fields (such as request.event.id).

Excluded PropertyReason
OccurredAtTimestamp is output separately as @timestamp
EventIdOutput separately as request.event.id
CorrelationIdManaged separately as standard correlation ID field
CausationIdManaged separately as standard causation ID field

PascalCase properties pushed via LogContext.PushProperty without the ctx. prefix are automatically converted to ctx.snake_case by OpenSearchJsonFormatter.

CustomerId → ctx.customer_id
OrderLineCount → ctx.order_line_count

Code generated by the Source Generator already includes the ctx. prefix and is therefore not subject to safety net conversion. This conversion only applies when LogContext.PushProperty is called manually.

Generated Enricher classes are partial class and provide the following extension points.

Enricher TypePartial MethodCall Timing
UsecaseOnEnrichRequest(request, disposables)At Request processing start
UsecaseOnEnrichResponse(request, response, disposables)At Response processing completion
DomainEventOnEnrich(domainEvent, disposables)Before Handler execution
Enricher TypeHelper MethodGenerated Field Pattern
UsecasePushRequestCtx(disposables, fieldName, value, pillars)ctx.{type}.request.{fieldName}
UsecasePushResponseCtx(disposables, fieldName, value, pillars)ctx.{type}.response.{fieldName}
DomainEventPushEventCtx(disposables, fieldName, value, pillars)ctx.{event}.{fieldName}
CommonPushRootCtx(disposables, fieldName, value, pillars)ctx.{fieldName} — generated only when [CtxRoot] attribute exists
Diagnostic CodeSeverityConditionDescription
FUNCTORIUM002WarningDifferent OpenSearch type groups assigned to same ctx field namee.g., ctx.customer_id is keyword in enricher A, long in enricher B
FUNCTORIUM003WarningRequest type has private/protected access restrictionApply [CtxIgnore] to the Request record to suppress the warning
FUNCTORIUM004WarningEvent type has private/protected access restrictionApply [CtxIgnore] to the Event record to suppress the warning
FUNCTORIUM005WarningHigh cardinality type + MetricsTagCardinality explosion warning when specifying string/Guid/numeric as MetricsTag
FUNCTORIUM006ErrorNon-numeric type + MetricsValueError when specifying boolean/keyword as MetricsValue
FUNCTORIUM007WarningMetricsTag + MetricsValue specified simultaneouslyWarning when both Tag and Value are specified on the same property
Integration PointCall SiteDescription
CtxEnricherPipelineApplication Layer (first in pipeline)IUsecaseCtxEnricher<TRequest, TResponse> DI injection. Simultaneous 3-Pillar propagation via EnrichRequest/EnrichResponse calls
ObservableDomainEventNotificationPublisherDomainEvent HandlerResolves IDomainEventCtxEnricher<TEvent> at runtime then calls Enrich
UsecaseMetricsPipelineApplication LayerReads ctx.* MetricsTag from MetricsTagContext and merges into existing TagList
LogTestContextTestingCaptures and verifies ctx.* fields via enrichFromLogContext: true option

Field NameApplication LayerAdapter LayerDescription
Static Fields
request.layer"application""adapter"Request layer identifier
request.category.name"usecase"Category nameRequest category identifier
request.category.type"command" / "query"-CQRS type
request.handler.nameHandler nameHandler nameHandler class name
request.handler.method"Handle"Method nameHandler method name
response.status"success" / "failure""success" / "failure"Response status
response.elapsedElapsed time (seconds)Elapsed time (seconds)Elapsed time (seconds)
error.type"expected" / "exceptional" / "aggregate""expected" / "exceptional" / "aggregate"Error classification
error.codeError codeError codeDomain-specific error code
@errorError object (structured)Error object (structured)Error data (detailed)
Dynamic Fields
@request.messageFull Command/Query objectFull parameter object (Debug)Request message
@response.messageFull response objectMethod return value (Debug)Response message
@request.params-Type-filtered parameter complex object (Info/Debug)Request parameters
EventLog LevelApplication LayerAdapter LayerDescription
RequestInformation1001 application.request2001 adapter.requestRequest received
Request (Debug)Debug-2001 adapter.requestRequest with parameter values
Response SuccessInformation1002 application.response.success2002 adapter.response.successSuccess response
Response Success (Debug)Debug-2002 adapter.response.successResponse with result values
Response WarningWarning1003 application.response.warning2003 adapter.response.warningExpected error (business logic)
Response ErrorError1004 application.response.error2004 adapter.response.errorExceptional error (system failure)
# Request
{request.layer} {request.category.name}.{request.category.type} {request.handler.name}.{request.handler.method} requesting with {@request.message}
# Response - Success
{request.layer} {request.category.name}.{request.category.type} {request.handler.name}.{request.handler.method} responded {response.status} in {response.elapsed:0.0000} s with {@response.message}
# Response - Warning/Error
{request.layer} {request.category.name}.{request.category.type} {request.handler.name}.{request.handler.method} responded {response.status} in {response.elapsed:0.0000} s with {error.type}:{error.code} {@error}
# Request (Information) - 5 params
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} {@request.params} requesting
# Request (Debug) - 6 params
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} {@request.params} requesting with {@request.message}
# Response (Information) - 6 params
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} responded {response.status} in {response.elapsed:0.0000} s
# Response (Debug) - 7 params
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} responded {response.status} in {response.elapsed:0.0000} s with {@response.message}
# Response Warning/Error
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} responded {response.status} in {response.elapsed:0.0000} s with {error.type}:{error.code} {@error}
LayerApproachTestingNotes
ApplicationDirect ILogger.LogXxx() callsUsecaseLoggingPipelineStructureTests7+ parameters exceed LoggerMessage.Define’s 6-parameter limit
AdapterLoggerMessage.Define delegatesObservableObservableSignalgingStructureTestsZero allocation, high performance

Application Usecase vs DomainEvent Publisher vs DomainEventHandler field comparison:

✅ = Supported, ✅ (conditional) = Conditionally supported, - = Not supported/Not applicable

FieldApplication UsecaseDomainEvent PublisherDomainEventHandler
request.layer"application""adapter""application"
request.category.name"usecase""event""usecase"
request.category.type"command" / "query"-"event"
request.handler.nameHandler class nameEvent/Aggregate type nameHandler class name
request.handler.method"Handle""Publish" / "PublishTrackedEvents""Handle"
@request.messageCommand/Query objectEvent objectEvent object
@response.messageResponse object--
request.event.count-✅ (Aggregate only)-
response.event.success_count-✅ (Partial Failure only)-
response.event.failure_count-✅ (Partial Failure only)-
response.status"success" / "failure""success" / "failure""success" / "failure"
response.elapsedElapsed time (seconds)Elapsed time (seconds)Elapsed time (seconds)
error.type"expected" / "exceptional" / "aggregate""expected" / "exceptional""expected" / "exceptional"
error.codeError codeError codeError code
@errorError objectError objectError object (Exception)

For error classification details, see the Error Classification section.

# Request - Single event
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} requesting with {@request.message}
# Request - Aggregate multiple events
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} requesting with {request.event.count} events
# Response - Success
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} responded {response.status} in {response.elapsed:0.0000} s
# Response - Success (Aggregate)
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} responded {response.status} in {response.elapsed:0.0000} s with {request.event.count} events
# Response - Warning/Error
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} responded {response.status} in {response.elapsed:0.0000} s with {error.type}:{error.code} {@error}
# Response - Warning/Error (Aggregate)
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} responded {response.status} in {response.elapsed:0.0000} s with {request.event.count} events with {error.type}:{error.code} {@error}
# Response - Partial Failure (Aggregate)
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} responded {response.status} in {response.elapsed:0.0000} s with {request.event.count} events partial failure: {response.event.success_count} succeeded, {response.event.failure_count} failed

DomainEvent Publisher is classified as the Adapter layer, so it uses the same Event IDs as the Adapter layer.

EventIDName
Request2001adapter.request
Success2002adapter.response.success
Warning2003adapter.response.warning
Error2004adapter.response.error

DomainEventHandler logging is from the Handler perspective, processing events published by the Publisher. request.layer is "application", request.category.name is "usecase", and request.category.type is "event".

# Request
{request.layer} {request.category.name}.{request.category.type} {request.handler.name}.{request.handler.method} {request.event.type} {request.event.id} requesting with {@request.message}
# Response - Success
{request.layer} {request.category.name}.{request.category.type} {request.handler.name}.{request.handler.method} {request.event.type} {request.event.id} responded {response.status} in {response.elapsed:0.0000} s
# Response - Warning/Error
{request.layer} {request.category.name}.{request.category.type} {request.handler.name}.{request.handler.method} {request.event.type} {request.event.id} responded {response.status} in {response.elapsed:0.0000} s with {error.type}:{error.code} {@error}

DomainEventHandler is classified as a usecase in the Application Layer, so it uses the same Event IDs as the Application Layer.

EventIDName
Request1001application.request
Success1002application.response.success
Warning1003application.response.warning
Error1004application.response.error
LayerApproachTestingNotes
DomainEvent PublisherDecoratorDomainEventPublisherLoggingStructureTestsAdapter layer pattern
DomainEvent HandlerINotificationPublisherDomainEventHandlerLoggingStructureTestsApplication Layer pattern
DomainEvent Handler EnricherIDomainEventCtxEnricher<TEvent>DomainEventHandlerEnricherLoggingStructureTestsCtxEnricherContext-based 3-Pillar Enrichment

LayerMeter Name PatternExample (ServiceNamespace = "mycompany.production")
Application{service.namespace}.applicationmycompany.production.application
Adapter{service.namespace}.adapter.{category}mycompany.production.adapter.repository
DomainEvent Publisher{service.namespace}.adapter.eventmycompany.production.adapter.event
DomainEvent Handler{service.namespace}.applicationmycompany.production.application
InstrumentApplication LayerAdapter LayerDomainEvent PublisherDomainEvent HandlerTypeUnit
requestsapplication.usecase.{type}.requestsadapter.{category}.requestsadapter.event.requestsapplication.usecase.event.requestsCounter{request}
responsesapplication.usecase.{type}.responsesadapter.{category}.responsesadapter.event.responsesapplication.usecase.event.responsesCounter{response}
durationapplication.usecase.{type}.durationadapter.{category}.durationadapter.event.durationapplication.usecase.event.durationHistograms
Tag KeyrequestCounterdurationHistogramresponseCounter (success)responseCounter (failure)
request.layer"application""application""application""application"
request.category.name"usecase""usecase""usecase""usecase"
request.category.type"command" / "query""command" / "query""command" / "query""command" / "query"
request.handler.nameHandler nameHandler nameHandler nameHandler name
request.handler.method"Handle""Handle""Handle""Handle"
response.status--"success""failure"
error.type---"expected" / "exceptional" / "aggregate"
error.code---Primary error code
Total Tags5568
Tag KeyrequestCounterdurationHistogramresponseCounter (success)responseCounter (failure)
request.layer"adapter""adapter""adapter""adapter"
request.category.nameCategory nameCategory nameCategory nameCategory name
request.handler.nameHandler nameHandler nameHandler nameHandler name
request.handler.methodMethod nameMethod nameMethod nameMethod name
response.status--"success""failure"
error.type---"expected" / "exceptional" / "aggregate"
error.code---Error code
Total Tags4457

For error classification details, see the Error Classification section.

Tag KeyrequestCounterdurationHistogramresponseCounter (success)responseCounter (failure)
request.layer"adapter""adapter""adapter""adapter"
request.category.name"event""event""event""event"
request.handler.nameHandler nameHandler nameHandler nameHandler name
request.handler.methodMethod nameMethod nameMethod nameMethod name
response.status--"success""failure"
error.type---"expected" / "exceptional"
error.code---Error code
Total Tags4457

Tags excluded from DomainEvent Metrics: request.event.count, response.event.success_count, response.event.failure_count are not used as Metrics tags. These values each have unique numeric values, so using them as tags would cause high cardinality explosion. This follows the same principle as not using response.elapsed as a Metrics tag.

Tag KeyrequestCounterdurationHistogramresponseCounter (success)responseCounter (failure)
request.layer"application""application""application""application"
request.category.name"usecase""usecase""usecase""usecase"
request.category.type"event""event""event""event"
request.handler.nameHandler nameHandler nameHandler nameHandler name
request.handler.method"Handle""Handle""Handle""Handle"
response.status--"success""failure"
error.type---"expected" / "exceptional"
error.code---Error code
Total Tags5568
LayerApproachTestingNotes
ApplicationIPipelineBehavior + IMeterFactoryUsecaseMetricsPipelineStructureTestsMediator pipeline
AdapterSource GeneratorObservablePortMetricsStructureTestsAuto-generated metrics instruments
DomainEvent PublisherDecorator + IMeterFactoryDomainEventPublisherMetricsStructureTestsAdapter layer pattern
DomainEvent HandlerINotificationPublisher + IMeterFactoryDomainEventHandlerMetricsStructureTestsApplication Layer pattern

PropertyApplication LayerAdapter Layer
Span Name{layer} {category}.{type} {handler}.{method}{layer} {category} {handler}.{method}
Exampleapplication usecase.command CreateOrderCommandHandler.Handleadapter repository OrderRepository.GetById
KindInternalInternal

Span Name format difference: Application Layer includes the .{type} segment (command/query/event), but the Adapter layer omits the .{type} segment since there is no CQRS type distinction.

Tag KeyApplication LayerAdapter LayerDescription
Request Tags
request.layer"application""adapter"Layer identifier
request.category.name"usecase"Category nameCategory identifier
request.category.type"command" / "query"-CQRS type
request.handler.nameHandler nameHandler nameHandler class name
request.handler.method"Handle"Method nameMethod name
Response Tags
response.status"success" / "failure""success" / "failure"Response status
response.elapsedElapsed time (seconds)Elapsed time (seconds)Elapsed time (seconds)
Error Tags
error.type"expected" / "exceptional" / "aggregate""expected" / "exceptional" / "aggregate"Error classification
error.codeError codeError codeError code
ActivityStatusOk / ErrorOk / ErrorOpenTelemetry status

For error classification details, see the Error Classification section.

PropertyPublishPublishTrackedEvents
Span Nameadapter event {EventType}.Publishadapter event PublishTrackedEvents.PublishTrackedEvents
KindInternalInternal
Tag KeyRequestSuccess ResponseFailure Response
request.layer"adapter""adapter""adapter"
request.category.name"event""event""event"
request.handler.nameevent type nameevent type nameevent type name
request.handler.method"Publish""Publish""Publish"
response.elapsed-Elapsed time (seconds)Elapsed time (seconds)
response.status-"success""failure"
error.type--"expected" / "exceptional"
error.code--Error code
Total Tags468

Publisher Tag Structure (PublishTrackedEvents)

Section titled “Publisher Tag Structure (PublishTrackedEvents)”
Tag KeyRequestSuccessPartial FailureTotal Failure
request.layer"adapter""adapter""adapter""adapter"
request.category.name"event""event""event""event"
request.handler.name"PublishTrackedEvents""PublishTrackedEvents""PublishTrackedEvents""PublishTrackedEvents"
request.handler.method"PublishTrackedEvents""PublishTrackedEvents""PublishTrackedEvents""PublishTrackedEvents"
request.aggregate.countaggregate countaggregate countaggregate countaggregate count
request.event.countevent countevent countevent countevent count
response.elapsed-Elapsed time (seconds)Elapsed time (seconds)Elapsed time (seconds)
response.status-"success""failure""failure"
response.event.success_count--success count-
response.event.failure_count--failure count-
error.type---"expected" / "exceptional"
error.code---Error code
Total Tags681010
PropertyDescription
Span Nameapplication usecase.event {HandlerName}.Handle
KindInternal
Tag KeySuccessFailure
request.layer"application""application"
request.category.name"usecase""usecase"
request.category.type"event""event"
request.handler.namehandler namehandler name
request.handler.method"Handle""Handle"
request.event.typeevent type nameevent type name
request.event.idevent idevent id
response.status"success""failure"
error.type-"expected" / "exceptional"
error.code-Error code
Total Tags810

Note: Handler’s response.elapsed is not set on Activity tags (Logging only).

LayerApproachTestingNotes
ApplicationIPipelineBehavior + ActivitySource.StartActivity()UsecaseTracingPipelineStructureTestsMediator pipeline
AdapterSource GeneratorObservablePortTracingStructureTestsAuto-generated Activity spans
DomainEvent PublisherDecorator + ActivitySource.StartActivity()DomainEventPublisherTracingStructureTestsAdapter layer pattern
DomainEvent HandlerINotificationPublisher + ActivitySource.StartActivity()DomainEventHandlerTracingStructureTestsApplication Layer pattern

ComponentFile Path
Field Name Generation HelperSrc/Functorium.SourceGenerators/Generators/ObservablePortGenerator/CollectionTypeHelper.cs
Application LoggingSrc/Functorium.Adapters/Observabilities/Pipelines/UsecaseLoggingPipeline.cs
Adapter LoggingSource Generator generated code
Application MetricsSrc/Functorium.Adapters/Observabilities/Pipelines/UsecaseMetricsPipeline.cs
Application TracingSrc/Functorium.Adapters/Observabilities/Pipelines/UsecaseTracingPipeline.cs
DomainEvent PublisherSrc/Functorium.Adapters/Observabilities/Events/ObservableDomainEventPublisher.cs
Custom Pipeline MarkerSrc/Functorium.Adapters/Observabilities/Pipelines/ICustomUsecasePipeline.cs
Ctx Enricher PipelineSrc/Functorium.Adapters/Observabilities/Pipelines/CtxEnricherPipeline.cs
Ctx Enricher InterfaceSrc/Functorium/Applications/Observabilities/IUsecaseCtxEnricher.cs
DomainEvent Ctx Enricher InterfaceSrc/Functorium/Applications/Observabilities/IDomainEventCtxEnricher.cs
CtxEnricher Source GeneratorSrc/Functorium.SourceGenerators/Generators/CtxEnricherGenerator/CtxEnricherGenerator.cs
DomainEvent CtxEnricher Source GeneratorSrc/Functorium.SourceGenerators/Generators/DomainEventCtxEnricherGenerator/DomainEventCtxEnricherGenerator.cs
CtxEnricherContextSrc/Functorium/Applications/Observabilities/CtxEnricherContext.cs
MetricsTagContextSrc/Functorium.Adapters/Observabilities/Contexts/MetricsTagContext.cs
CtxPillar enumSrc/Functorium/Applications/Observabilities/CtxPillar.cs
CtxRoot AttributeSrc/Functorium/Applications/Observabilities/CtxRootAttribute.cs
CtxIgnore AttributeSrc/Functorium/Applications/Observabilities/CtxIgnoreAttribute.cs
CtxTarget AttributeSrc/Functorium/Applications/Observabilities/CtxTargetAttribute.cs
Tracing Custom BaseSrc/Functorium.Adapters/Observabilities/Pipelines/UsecaseTracingCustomPipelineBase.cs
Metric Custom BaseSrc/Functorium.Adapters/Observabilities/Pipelines/UsecaseMetricCustomPipelineBase.cs
Pipeline ConfigurationSrc/Functorium.Adapters/Observabilities/Builders/Configurators/PipelineConfigurator.cs
TestFile Path
Application Logging StructureTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Pipelines/UsecaseLoggingPipelineStructureTests.cs
Adapter Logging StructureTests/Functorium.Tests.Unit/AdaptersTests/SourceGenerators/ObservableObservableSignalgingStructureTests.cs
Application Metrics StructureTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Pipelines/UsecaseMetricsPipelineStructureTests.cs
Adapter Metrics StructureTests/Functorium.Tests.Unit/AdaptersTests/SourceGenerators/ObservablePortMetricsStructureTests.cs
Application Tracing StructureTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Pipelines/UsecaseTracingPipelineStructureTests.cs
Adapter Tracing StructureTests/Functorium.Tests.Unit/AdaptersTests/SourceGenerators/ObservablePortTracingStructureTests.cs
DomainEvent Publisher LoggingTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Events/DomainEventPublisherLoggingStructureTests.cs
DomainEvent Handler LoggingTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Events/DomainEventHandlerLoggingStructureTests.cs
CtxEnricher Source GeneratorTests/Functorium.Tests.Unit/AdaptersTests/SourceGenerators/CtxEnricherGeneratorTests.cs
DomainEvent CtxEnricher Source GeneratorTests/Functorium.Tests.Unit/AdaptersTests/SourceGenerators/DomainEventCtxEnricherGeneratorTests.cs
Ctx Enricher IntegrationTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Pipelines/UsecaseLoggingPipelineEnricherTests.cs
DomainEvent Handler Enricher LoggingTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Events/DomainEventHandlerEnricherLoggingStructureTests.cs
DomainEvent Handler Enricher MetricsTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Events/DomainEventHandlerMetricsStructureTests.cs
DomainEvent Handler Enricher TracingTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Events/DomainEventHandlerTracingStructureTests.cs
Tracing Custom BaseTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Pipelines/UsecaseTracingCustomPipelineBaseTests.cs
Pipeline ConfigurationTests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Configurators/PipelineConfiguratorTests.cs

ObservableSignal — Internal Developer Logging API for Adapter Implementations

Section titled “ObservableSignal — Internal Developer Logging API for Adapter Implementations”

ObservableSignal is a static API for developers to directly emit operational logs within Adapter implementation code. The common context set by the Observable wrapper (request.layer, request.category.name, request.handler.name, request.handler.method) is automatically included.

✅ = Supported, X = Not supported

LevelLoggingTracing (Activity Event)Metrics
DebugX (high frequency -> noise)X
Warning✅ (track degradation cause within span)X
Error✅ (track failure cause within span)X
  • Metrics excluded: The Observable wrapper already auto-generates request/response/duration metrics. Use IMeterFactory directly for custom metrics.
  • Tracing excluded at Debug level: Adding high-frequency events like cache misses to spans creates trace noise.
EventIdNameLevelDescription
2021adapter.signal.debugDebugNormal flow details (cache miss, query details)
2022adapter.signal.warningWarningAuto-recoverable degradation (retry, fallback, rate limit)
2023adapter.signal.errorErrorUnrecoverable failure (retries exhausted, circuit open)
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} — {adapter.log.message} {@adapter.log.context}
PrefixPurposeExample
adapter.retry.*Retry-relatedadapter.retry.attempt, adapter.retry.delay_ms
adapter.http.*HTTP-relatedadapter.http.status_code, adapter.http.retry_after_seconds
adapter.message.*Message broker-relatedadapter.message.id, adapter.message.queue
adapter.db.*Database-relatedadapter.db.elapsed_ms, adapter.db.operation
adapter.cache.*Cache-relatedadapter.cache.key, adapter.cache.provider
// Warning on Polly retry
ObservableSignal.Warning("Retry attempt {Attempt}/{MaxRetry} after {Delay}s delay",
("adapter.retry.attempt", attempt),
("adapter.retry.delay_ms", delay.TotalMilliseconds));
// Debug on cache miss (high frequency)
ObservableSignal.Debug("Cache miss", ("adapter.cache.key", cacheKey));
// Error when retries exhausted
ObservableSignal.Error(ex, "Database operation failed after exhausting retries",
("adapter.db.retry.attempt", maxRetries));
  1. [GenerateObservablePort] Source Generator calls ObservableSignalScope.Begin() within ExecuteWithSpan
  2. ObservableSignalScope sets current context (logger, layer, category, handler, method) via AsyncLocal
  3. When ObservableSignal.Debug/Warning/Error is called in Adapter code, common fields are obtained from ObservableSignalScope.Current
  4. ObservableSignalFactory outputs via ILogger + Activity Event
TestFile
ObservableSignal API + ScopeTests/Functorium.Tests.Unit/DomainsTests/Observabilities/ObservableSignalTests.cs