Skip to main content

Telemetry Standards

Consistent telemetry practices ensure effective monitoring, troubleshooting, and analysis of application performance and behavior. This guide outlines the standards for implementing telemetry across all applications.

OpenTelemetry Requirements

All applications must use OpenTelemetry as the standard telemetry framework. OpenTelemetry provides a comprehensive set of tools, APIs, and SDKs for collecting, processing, and exporting telemetry data.

Key Requirements

  • Use OpenTelemetry SDK for all instrumentation needs
  • Follow the W3C Trace Context specification for context propagation
  • Configure exporters to send telemetry data to your organization's telemetry backend
  • Implement automatic instrumentation where available
  • Add custom instrumentation for business-critical operations

Signal Types

Implement all three signal types provided by OpenTelemetry:

  1. Traces: To track request flows through distributed systems
  2. Metrics: To measure system performance and behavior
  3. Logs: To capture detailed information about application events

OpenTelemetry Collector

Deploy the OpenTelemetry Collector as an agent or gateway to:

  • Collect telemetry data from multiple services
  • Process and transform data (filtering, batching, etc.)
  • Export data to multiple destinations simultaneously

Logging Best Practices

Naming Conventions

When implementing logging, adhere to these naming conventions:

  • Use PascalCase for Named Placeholders in log messages
  • Apply consistent terminology across all log entries
  • Keep property names descriptive and concise

Value Casing Standards

For telemetry values:

  • Use lowercase invariant for all property values unless casing is required for uniqueness
  • Maintain original casing only when it differentiates identity (e.g., case-sensitive identifiers)
  • Normalize all status codes, error types, and general string values to lowercase

Standard Identifiers

Always include the following standard identifiers in your telemetry for tracking related events across distributed systems:

IdentifierProperty NameDescription
Correlation IDcoaxle.correlation_idTracks a request across multiple services
Party IDcoaxle.party_idIdentifies the user or entity associated with the event
Tenant IDcoaxle.tenant_idIdentifies the tenant context for multi-tenant applications

Implementation Examples

Named Placeholders

Always use named placeholders instead of positional placeholders. Named placeholders improve readability and maintainability of logging code.

// Correct: Using PascalCase named placeholders
logger.LogInformation("User {UserId} accessed resource {ResourceId} with permission level {PermissionLevel}",
userId, resourceId, permissionLevel);

// Incorrect: Using positional placeholders
logger.LogInformation("User {0} accessed resource {1} with permission level {2}",
userId, resourceId, permissionLevel);

// Incorrect: Using non-PascalCase named placeholders
logger.LogInformation("User {userId} accessed resource {resourceId} with permission level {permissionLevel}",
userId, resourceId, permissionLevel);

Using Standard Identifiers

Always include standard identifiers in your telemetry data:

using Microsoft.Extensions.Logging;

public class OrderService
{
private readonly ILogger<OrderService> _logger;

public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}

public void ProcessOrder(Order order, string correlationId, string partyId, string tenantId)
{
// Add identifiers to logging scope
using (_logger.BeginScope(new Dictionary<string, object>
{
["coaxle.correlation_id"] = correlationId,
["coaxle.party_id"] = partyId,
["coaxle.tenant_id"] = tenantId
}))
{
_logger.LogInformation("Processing order {OrderId} for customer {CustomerId}",
order.Id, order.CustomerId);

// Processing logic here

// Using lowercase for status values
var status = "completed";
_logger.LogInformation("Order {OrderId} processed successfully with {ItemCount} items and status {Status}",
order.Id, order.Items.Count, status);
}
}
}

OpenTelemetry Implementation

// Initialize OpenTelemetry in your application
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using OpenTelemetry.Logs;

// In Startup.cs or Program.cs
public void ConfigureServices(IServiceCollection services)
{
// Configure OpenTelemetry with the required resource attributes
services.AddOpenTelemetry()
.ConfigureResource(builder => builder
.AddService("my-service-name", serviceVersion: "1.0.0")
.AddAttributes(new Dictionary<string, object>
{
["deployment.environment"] = "production" // Lowercase invariant value
}))
.WithTracing(builder => builder
// Add instrumentation for ASP.NET Core, HttpClient, etc.
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
// Add custom span processor for correlation IDs
.AddProcessor(new CorrelationIdProcessor("coaxle.correlation_id", "coaxle.party_id", "coaxle.tenant_id"))
.AddOtlpExporter()) // Send to OpenTelemetry collector
.WithMetrics(builder => builder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddOtlpExporter());

// Configure OpenTelemetry logging
services.AddLogging(builder =>
{
builder.AddOpenTelemetry(options =>
{
options.AddOtlpExporter();
options.IncludeFormattedMessage = true;
options.IncludeScopes = true;
});
});
}

// Example of creating a custom span
public async Task ProcessOrderWithTracing(Order order, string correlationId, string partyId, string tenantId)
{
var activitySource = new ActivitySource("MyCompany.OrderProcessing");

// Start a new span
using var activity = activitySource.StartActivity("ProcessOrder");

// Add standard identifiers as attributes
activity?.SetTag("coaxle.correlation_id", correlationId);
activity?.SetTag("coaxle.party_id", partyId);
activity?.SetTag("coaxle.tenant_id", tenantId);

// Add business context - unique identifiers keep original casing if needed
activity?.SetTag("order.id", order.Id);
activity?.SetTag("order.customer_id", order.CustomerId);

// Add a status attribute with lowercase invariant value
activity?.SetTag("order.status", "processing");

try
{
// Process the order
await ProcessOrderInternal(order);

// Record success
activity?.SetStatus(ActivityStatusCode.Ok);
activity?.SetTag("order.status", "completed"); // Lowercase invariant value
}
catch (Exception ex)
{
// Record error
activity?.SetStatus(ActivityStatusCode.Error);
activity?.RecordException(ex);
activity?.SetTag("order.status", "failed"); // Lowercase invariant value
throw;
}
}

Configuring OpenTelemetry Collector

Deploy the OpenTelemetry Collector using the following configuration template:

receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318

processors:
batch:
send_batch_size: 1000
timeout: 10s
attributes:
actions:
- key: deployment.environment
action: upsert
value: ${DEPLOYMENT_ENVIRONMENT}
resource:
attributes:
- key: service.namespace
value: "coaxle"
action: upsert

exporters:
otlp:
endpoint: "your-telemetry-backend:4317"
tls:
insecure: false
ca_file: /etc/ssl/certs/ca-certificates.crt
logging:
verbosity: detailed

service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, attributes, resource]
exporters: [otlp, logging]
metrics:
receivers: [otlp]
processors: [batch, attributes, resource]
exporters: [otlp, logging]
logs:
receivers: [otlp]
processors: [batch, attributes, resource]
exporters: [otlp, logging]

Log Levels

Use appropriate log levels to categorize the severity and importance of log messages:

LevelWhen to Use
TraceExtremely detailed information, useful only for pinpointing issues in a specific component
DebugDetailed information useful for debugging purposes
InformationGeneral information highlighting the progress of the application
WarningIndicates potential issues or unexpected behavior that doesn't cause application failure
ErrorError conditions that affect a specific operation but not the overall application
CriticalCritical errors that require immediate attention and may cause application failure

Performance Considerations

  • Avoid excessive logging in high-throughput paths
  • Use asynchronous logging where appropriate
  • Consider sampling for high-volume telemetry
  • Implement log rotation and retention policies
  • Monitor the performance impact of telemetry implementations

Sensitive Information

Never log sensitive information such as:

  • Passwords or authentication tokens
  • Personal identifiable information (PII)
  • Financial data
  • Health information
  • Any data protected by privacy regulations

Instead, log references or masked versions of this information when necessary.

OpenTelemetry Semantic Conventions

Adhere to OpenTelemetry semantic conventions for consistent naming across services:

  • Use the OpenTelemetry Semantic Conventions for attribute naming
  • Follow standard naming for error attributes
  • Use standard semantic attributes for HTTP, database, and messaging operations
DomainAttribute PrefixExamples
HTTPhttp.http.method, http.status_code
Databasedb.db.system, db.statement
Messagingmessaging.messaging.system, messaging.destination
Exceptionexception.exception.type, exception.message
Custom Businesscoaxle.coaxle.correlation_id, coaxle.tenant_id

Validation and Testing

  • Verify telemetry data quality with automated tests
  • Implement telemetry validation as part of CI/CD pipelines
  • Set up monitoring alerts for missing or malformed telemetry data
  • Regularly review and optimize telemetry implementations