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 .
Concept Description Service Attributes service.namespace, service.name, etc. for OpenTelemetry standard service identificationError Classification expected (business error), exceptional (system error), aggregate (composite error)3-Pillar Field/Tag request.*, response.*, error.* fields used identically across Logging, Metrics, and TracingMeter Name {service.namespace}.{layer}[.{category}] patternInstrument requests (Counter), responses (Counter), duration (Histogram)Message Template Structured log message format per layer and Event ID scheme (Application 1001-1004, Adapter 2001-2004) Span Name {layer} {category}[.{type}] {handler}.{method}ctx.* Context Fields Source 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.
Attribute Description Example service.namespaceNamespace of service.name. Helps distinguish service groups (e.g., by team or environment). mycompany.productionservice.nameLogical name of the service. Must be identical across all horizontally scaled instances. orderserviceservice.versionVersion string of the service API or implementation. 2.0.0service.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 Case error.type error.code Description IHasErrorCode + IsExpected"expected"Error code Expected business logic error with error code IHasErrorCode + IsExceptional"exceptional"Error code Exceptional system error with error code ManyErrors"aggregate"Primary error code Multiple errors aggregated (Exceptional takes priority) Expected (LanguageExt)"expected"Type name LanguageExt default expected error without error code Exceptional (LanguageExt)"exceptional"Type name LanguageExt default exceptional error without error code
error.type and @error.ErrorType use different value formats for different purposes.
Error Type error.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.
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.
Namespace Description Example request.*Request-related information request.layer, request.handler.nameresponse.*Response-related information response.status, response.elapsederror.*Error-related information error.type, error.code
Category Rule Example Static fields (event-related) .countrequest.event.countDynamic fields (parameter size) _countrequest.params.orders_countAdjective/noun combination _countresponse.event.success_count, response.event.failure_count
request.event.count # Static .count
response.event.success_count # Adjective combination _count
request.params.orders_count # Dynamic parameter _count
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/Tag Logging Metrics Tracing Description request.layer✅ ✅ ✅ Architecture layer ("application") request.category.name✅ ✅ ✅ Request category ("usecase") request.category.type✅ ✅ ✅ CQRS type ("command", "query") request.handler.name✅ ✅ ✅ Handler class name request.handler.method✅ ✅ ✅ Handler method name ("Handle") response.status✅ ✅ ✅ Response status ("success", "failure") response.elapsed✅ -* ✅ Elapsed time (seconds) error.type✅ ✅ ✅ Error classification ("expected", "exceptional", "aggregate") error.code✅ ✅ ✅ Domain-specific error code @error✅ - - Structured error object (detailed)
Adapter Layer: (Unit tests: Logging , Metrics , Tracing )
Field/Tag Logging Metrics Tracing Description request.layer✅ ✅ ✅ Architecture layer ("adapter") request.category.name✅ ✅ ✅ Category (e.g., "repository") request.handler.name✅ ✅ ✅ Handler class name request.handler.method✅ ✅ ✅ Handler method name response.status✅ ✅ ✅ Response status ("success", "failure") response.elapsed✅ -* ✅ Elapsed time (seconds) error.type✅ ✅ ✅ Error classification ("expected", "exceptional", "aggregate") error.code✅ ✅ ✅ Domain-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/Tag Logging Metrics Tracing Description request.layer✅ ✅ ✅ Architecture layer ("adapter") request.category.name✅ ✅ ✅ Request category ("event") request.handler.name✅ ✅ ✅ Event type name or Aggregate type name request.handler.method✅ ✅ ✅ Method name ("Publish", "PublishTrackedEvents") request.aggregate.count- - ✅ Number of Aggregate types (PublishTrackedEvents only) request.event.count✅ - ✅ Number of events in batch publishing (Aggregate only) response.status✅ ✅ ✅ Response 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.type✅ ✅ ✅ Error classification ("expected", "exceptional") error.code✅ ✅ ✅ Domain-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/Tag Logging Metrics Tracing Description request.layer✅ ✅ ✅ Architecture layer ("application") request.category.name✅ ✅ ✅ Request category ("usecase") request.category.type✅ ✅ ✅ CQRS type ("event") request.handler.name✅ ✅ ✅ Handler class name request.handler.method✅ ✅ ✅ Method name ("Handle") request.event.type✅ - ✅ Event type name request.event.id✅ - ✅ Event unique ID @request.message✅ - - Event object (on request) response.status✅ ✅ ✅ Response status ("success", "failure") response.elapsed✅ -* - Elapsed time (seconds) error.type✅ ✅ ✅ Error classification ("expected", "exceptional") error.code✅ ✅ ✅ Domain-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.* 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.
Item Description Target Pillar Logging + 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 Mechanism CtxEnricherContext.Push(name, value, pillars) — simultaneous propagation to Logging/Tracing/MetricsTarget Properties Public scalar and collection properties (complex types are excluded) Pipeline Order CtxEnricher → Metrics → Tracing → Logging → Validation → ... → Handler
Pillar Mechanism Description Logging Serilog LogContext.PushProperty Output as structured log field Tracing Activity.Current?.SetTagOutput as Span Attribute MetricsTag MetricsTagContext (AsyncLocal) → TagList mergeAdded as dimension to existing Counter/Histogram MetricsValue Record value to separate Histogram instrument Record numeric fields as targets for statistical aggregation
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,
Scope Pattern Example (C# to ctx field) Usecase Request ctx.{containing_type}.request.{property}PlaceOrderCommand.Request.CustomerId → ctx.place_order_command.request.customer_idUsecase Response ctx.{containing_type}.response.{property}PlaceOrderCommand.Response.OrderId → ctx.place_order_command.response.order_idDomainEvent (top-level) ctx.{event}.{property}OrderPlacedEvent.CustomerId → ctx.order_placed_event.customer_idDomainEvent (nested) ctx.{containing_type}.{event}.{property}Order.CreatedEvent.OrderId → ctx.order.created_event.order_idInterface scope ctx.{interface (I removed)}.{property}IRegional.RegionCode → ctx.regional.region_code[CtxRoot] promotionctx.{property}[CtxRoot] CustomerId → ctx.customer_idCollection above rules}_count suffixList<OrderLine> Lines → ctx.place_order_command.request.lines_count
All names are converted from PascalCase to snake_case. The I prefix is removed from interface names (ICustomerRequest → customer_request, IX → x).
Field names are identical across all Pillars — the same ctx.* names are used in Logging, Tracing, and Metrics.
[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 (
[CtxTarget( CtxPillar . Default | CtxPillar . MetricsValue )]
decimal TotalAmount // L+T + Histogram recording
✅ = Supported, - = Not supported/Not applicable
ctx Field Type Logging Tracing Metrics Tag Metrics 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# Type OpenSearch Type Group Notes boolbooleanbyte, sbyte, short, ushort, int, uint, long, ulonglongfloat, double, decimaldoublestringkeywordGuid, DateTime, DateTimeOffset, TimeSpan, DateOnly, TimeOnly, UrikeywordenumkeywordOption<T> (LanguageExt)keywordIValueObject, 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).
Attribute Target Effect [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
[CtxTarget] specified? → YES → Specified Pillar
Default → CtxPillar.Default (Logging + Tracing)
Property/parameter level settings always take priority over interface level settings.
Cardinality Level Applicable Types MetricsTag MetricsValue FixedboolSafe Not allowed BoundedLowenumConditional Not allowed Unboundedstring, Guid, DateTime, IValueObject, IEntityId<T>, Option<T>Warning (FUNCTORIUM005)Not allowed Numericint, long, decimal, doubleWarning (FUNCTORIUM005)Allowed NumericCountCollection count Warning (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 Property Reason 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 Type Partial Method Call Timing Usecase OnEnrichRequest(request, disposables)At Request processing start Usecase OnEnrichResponse(request, response, disposables)At Response processing completion DomainEvent OnEnrich(domainEvent, disposables)Before Handler execution
Enricher Type Helper Method Generated Field Pattern Usecase PushRequestCtx(disposables, fieldName, value, pillars)ctx.{type}.request.{fieldName}Usecase PushResponseCtx(disposables, fieldName, value, pillars)ctx.{type}.response.{fieldName}DomainEvent PushEventCtx(disposables, fieldName, value, pillars)ctx.{event}.{fieldName}Common PushRootCtx(disposables, fieldName, value, pillars)ctx.{fieldName} — generated only when [CtxRoot] attribute exists
Diagnostic Code Severity Condition Description FUNCTORIUM002Warning Different OpenSearch type groups assigned to same ctx field name e.g., ctx.customer_id is keyword in enricher A, long in enricher B FUNCTORIUM003Warning Request type has private/protected access restriction Apply [CtxIgnore] to the Request record to suppress the warning FUNCTORIUM004Warning Event type has private/protected access restriction Apply [CtxIgnore] to the Event record to suppress the warning FUNCTORIUM005Warning High cardinality type + MetricsTag Cardinality explosion warning when specifying string/Guid/numeric as MetricsTag FUNCTORIUM006Error Non-numeric type + MetricsValue Error when specifying boolean/keyword as MetricsValue FUNCTORIUM007Warning MetricsTag + MetricsValue specified simultaneouslyWarning when both Tag and Value are specified on the same property
Integration Point Call Site Description CtxEnricherPipelineApplication Layer (first in pipeline) IUsecaseCtxEnricher<TRequest, TResponse> DI injection. Simultaneous 3-Pillar propagation via EnrichRequest/EnrichResponse callsObservableDomainEventNotificationPublisherDomainEvent Handler Resolves IDomainEventCtxEnricher<TEvent> at runtime then calls Enrich UsecaseMetricsPipelineApplication Layer Reads ctx.* MetricsTag from MetricsTagContext and merges into existing TagList LogTestContextTesting Captures and verifies ctx.* fields via enrichFromLogContext: true option
Field Name Application Layer Adapter Layer Description Static Fields request.layer"application""adapter"Request layer identifier request.category.name"usecase"Category name Request category identifier request.category.type"command" / "query"- CQRS type request.handler.nameHandler name Handler name Handler class name request.handler.method"Handle"Method name Handler 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 code Error code Domain-specific error code @errorError object (structured) Error object (structured) Error data (detailed) Dynamic Fields @request.messageFull Command/Query object Full parameter object (Debug) Request message @response.messageFull response object Method return value (Debug) Response message @request.params- Type-filtered parameter complex object (Info/Debug) Request parameters
Event Log Level Application Layer Adapter Layer Description Request Information 1001 application.request 2001 adapter.request Request received Request (Debug) Debug - 2001 adapter.request Request with parameter values Response Success Information 1002 application.response.success 2002 adapter.response.success Success response Response Success (Debug) Debug - 2002 adapter.response.success Response with result values Response Warning Warning 1003 application.response.warning 2003 adapter.response.warning Expected error (business logic) Response Error Error 1004 application.response.error 2004 adapter.response.error Exceptional error (system failure)
{request.layer} {request.category.name}.{request.category.type} {request.handler.name}.{request.handler.method} requesting with {@request.message}
{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}
{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}
Application Usecase vs DomainEvent Publisher vs DomainEventHandler field comparison:
✅ = Supported, ✅ (conditional) = Conditionally supported, - = Not supported/Not applicable
Field Application Usecase DomainEvent Publisher DomainEventHandler request.layer"application""adapter""application"request.category.name"usecase""event""usecase"request.category.type"command" / "query"- "event"request.handler.nameHandler class name Event/Aggregate type name Handler class name request.handler.method"Handle""Publish" / "PublishTrackedEvents""Handle"@request.messageCommand/Query object Event object Event 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 code Error code Error code @errorError object Error object Error object (Exception)
For error classification details, see the Error Classification section.
{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
{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.
Event ID Name Request 2001 adapter.requestSuccess 2002 adapter.response.successWarning 2003 adapter.response.warningError 2004 adapter.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.layer} {request.category.name}.{request.category.type} {request.handler.name}.{request.handler.method} {request.event.type} {request.event.id} requesting with {@request.message}
{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.
Event ID Name Request 1001 application.requestSuccess 1002 application.response.successWarning 1003 application.response.warningError 1004 application.response.error
Layer Meter Name Pattern Example (ServiceNamespace = "mycompany.production") Application {service.namespace}.applicationmycompany.production.applicationAdapter {service.namespace}.adapter.{category}mycompany.production.adapter.repositoryDomainEvent Publisher {service.namespace}.adapter.eventmycompany.production.adapter.eventDomainEvent Handler {service.namespace}.applicationmycompany.production.application
Instrument Application Layer Adapter Layer DomainEvent Publisher DomainEvent Handler Type Unit requests application.usecase.{type}.requestsadapter.{category}.requestsadapter.event.requestsapplication.usecase.event.requestsCounter {request}responses application.usecase.{type}.responsesadapter.{category}.responsesadapter.event.responsesapplication.usecase.event.responsesCounter {response}duration application.usecase.{type}.durationadapter.{category}.durationadapter.event.durationapplication.usecase.event.durationHistogram s
Tag Key requestCounter durationHistogram responseCounter (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 name Handler name Handler name Handler name request.handler.method"Handle""Handle""Handle""Handle"response.status- - "success""failure"error.type- - - "expected" / "exceptional" / "aggregate"error.code- - - Primary error code Total Tags 5 5 6 8
Tag Key requestCounter durationHistogram responseCounter (success) responseCounter (failure) request.layer"adapter""adapter""adapter""adapter"request.category.nameCategory name Category name Category name Category name request.handler.nameHandler name Handler name Handler name Handler name request.handler.methodMethod name Method name Method name Method name response.status- - "success""failure"error.type- - - "expected" / "exceptional" / "aggregate"error.code- - - Error code Total Tags 4 4 5 7
For error classification details, see the Error Classification section.
Tag Key requestCounter durationHistogram responseCounter (success) responseCounter (failure) request.layer"adapter""adapter""adapter""adapter"request.category.name"event""event""event""event"request.handler.nameHandler name Handler name Handler name Handler name request.handler.methodMethod name Method name Method name Method name response.status- - "success""failure"error.type- - - "expected" / "exceptional"error.code- - - Error code Total Tags 4 4 5 7
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 Key requestCounter durationHistogram responseCounter (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 name Handler name Handler name Handler name request.handler.method"Handle""Handle""Handle""Handle"response.status- - "success""failure"error.type- - - "expected" / "exceptional"error.code- - - Error code Total Tags 5 5 6 8
Property Application Layer Adapter Layer Span Name {layer} {category}.{type} {handler}.{method}{layer} {category} {handler}.{method}Example application usecase.command CreateOrderCommandHandler.Handleadapter repository OrderRepository.GetByIdKind InternalInternal
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 Key Application Layer Adapter Layer Description Request Tags request.layer"application""adapter"Layer identifier request.category.name"usecase"Category name Category identifier request.category.type"command" / "query"- CQRS type request.handler.nameHandler name Handler name Handler class name request.handler.method"Handle"Method name Method 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 code Error code Error code ActivityStatus Ok / ErrorOk / ErrorOpenTelemetry status
For error classification details, see the Error Classification section.
Property Publish PublishTrackedEvents Span Name adapter event {EventType}.Publishadapter event PublishTrackedEvents.PublishTrackedEventsKind InternalInternal
Tag Key Request Success Response Failure Response request.layer"adapter""adapter""adapter"request.category.name"event""event""event"request.handler.nameevent type name event type name event 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 Tags 4 6 8
Tag Key Request Success Partial Failure Total 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 count aggregate count aggregate count aggregate count request.event.countevent count event count event count event 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 Tags 6 8 10 10
Property Description Span Name application usecase.event {HandlerName}.HandleKind Internal
Tag Key Success Failure request.layer"application""application"request.category.name"usecase""usecase"request.category.type"event""event"request.handler.namehandler name handler name request.handler.method"Handle""Handle"request.event.typeevent type name event type name request.event.idevent id event id response.status"success""failure"error.type- "expected" / "exceptional"error.code- Error code Total Tags 8 10
Note: Handler’s response.elapsed is not set on Activity tags (Logging only).
Component File Path Field Name Generation Helper Src/Functorium.SourceGenerators/Generators/ObservablePortGenerator/CollectionTypeHelper.csApplication Logging Src/Functorium.Adapters/Observabilities/Pipelines/UsecaseLoggingPipeline.csAdapter Logging Source Generator generated code Application Metrics Src/Functorium.Adapters/Observabilities/Pipelines/UsecaseMetricsPipeline.csApplication Tracing Src/Functorium.Adapters/Observabilities/Pipelines/UsecaseTracingPipeline.csDomainEvent Publisher Src/Functorium.Adapters/Observabilities/Events/ObservableDomainEventPublisher.csCustom Pipeline Marker Src/Functorium.Adapters/Observabilities/Pipelines/ICustomUsecasePipeline.csCtx Enricher Pipeline Src/Functorium.Adapters/Observabilities/Pipelines/CtxEnricherPipeline.csCtx Enricher Interface Src/Functorium/Applications/Observabilities/IUsecaseCtxEnricher.csDomainEvent Ctx Enricher Interface Src/Functorium/Applications/Observabilities/IDomainEventCtxEnricher.csCtxEnricher Source Generator Src/Functorium.SourceGenerators/Generators/CtxEnricherGenerator/CtxEnricherGenerator.csDomainEvent CtxEnricher Source Generator Src/Functorium.SourceGenerators/Generators/DomainEventCtxEnricherGenerator/DomainEventCtxEnricherGenerator.csCtxEnricherContext Src/Functorium/Applications/Observabilities/CtxEnricherContext.csMetricsTagContext Src/Functorium.Adapters/Observabilities/Contexts/MetricsTagContext.csCtxPillar enum Src/Functorium/Applications/Observabilities/CtxPillar.csCtxRoot Attribute Src/Functorium/Applications/Observabilities/CtxRootAttribute.csCtxIgnore Attribute Src/Functorium/Applications/Observabilities/CtxIgnoreAttribute.csCtxTarget Attribute Src/Functorium/Applications/Observabilities/CtxTargetAttribute.csTracing Custom Base Src/Functorium.Adapters/Observabilities/Pipelines/UsecaseTracingCustomPipelineBase.csMetric Custom Base Src/Functorium.Adapters/Observabilities/Pipelines/UsecaseMetricCustomPipelineBase.csPipeline Configuration Src/Functorium.Adapters/Observabilities/Builders/Configurators/PipelineConfigurator.cs
Test File Path Application Logging Structure Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Pipelines/UsecaseLoggingPipelineStructureTests.csAdapter Logging Structure Tests/Functorium.Tests.Unit/AdaptersTests/SourceGenerators/ObservableObservableSignalgingStructureTests.csApplication Metrics Structure Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Pipelines/UsecaseMetricsPipelineStructureTests.csAdapter Metrics Structure Tests/Functorium.Tests.Unit/AdaptersTests/SourceGenerators/ObservablePortMetricsStructureTests.csApplication Tracing Structure Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Pipelines/UsecaseTracingPipelineStructureTests.csAdapter Tracing Structure Tests/Functorium.Tests.Unit/AdaptersTests/SourceGenerators/ObservablePortTracingStructureTests.csDomainEvent Publisher Logging Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Events/DomainEventPublisherLoggingStructureTests.csDomainEvent Handler Logging Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Events/DomainEventHandlerLoggingStructureTests.csCtxEnricher Source Generator Tests/Functorium.Tests.Unit/AdaptersTests/SourceGenerators/CtxEnricherGeneratorTests.csDomainEvent CtxEnricher Source Generator Tests/Functorium.Tests.Unit/AdaptersTests/SourceGenerators/DomainEventCtxEnricherGeneratorTests.csCtx Enricher Integration Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Pipelines/UsecaseLoggingPipelineEnricherTests.csDomainEvent Handler Enricher Logging Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Events/DomainEventHandlerEnricherLoggingStructureTests.csDomainEvent Handler Enricher Metrics Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Events/DomainEventHandlerMetricsStructureTests.csDomainEvent Handler Enricher Tracing Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Events/DomainEventHandlerTracingStructureTests.csTracing Custom Base Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Pipelines/UsecaseTracingCustomPipelineBaseTests.csPipeline Configuration Tests/Functorium.Tests.Unit/AdaptersTests/Observabilities/Configurators/PipelineConfiguratorTests.cs
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
Level Logging Tracing (Activity Event) Metrics Debug ✅ X (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.
EventId Name Level Description 2021 adapter.signal.debugDebug Normal flow details (cache miss, query details) 2022 adapter.signal.warningWarning Auto-recoverable degradation (retry, fallback, rate limit) 2023 adapter.signal.errorError Unrecoverable failure (retries exhausted, circuit open)
{request.layer} {request.category.name} {request.handler.name}.{request.handler.method} — {adapter.log.message} {@adapter.log.context}
Prefix Purpose Example adapter.retry.*Retry-related adapter.retry.attempt, adapter.retry.delay_msadapter.http.*HTTP-related adapter.http.status_code, adapter.http.retry_after_secondsadapter.message.*Message broker-related adapter.message.id, adapter.message.queueadapter.db.*Database-related adapter.db.elapsed_ms, adapter.db.operationadapter.cache.*Cache-related adapter.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));
[GenerateObservablePort] Source Generator calls ObservableSignalScope.Begin() within ExecuteWithSpan
ObservableSignalScope sets current context (logger, layer, category, handler, method) via AsyncLocal
When ObservableSignal.Debug/Warning/Error is called in Adapter code, common fields are obtained from ObservableSignalScope.Current
ObservableSignalFactory outputs via ILogger + Activity Event
Test File ObservableSignal API + Scope Tests/Functorium.Tests.Unit/DomainsTests/Observabilities/ObservableSignalTests.cs