OpenTelemetry Integration
TickerQ.Instrumentation.OpenTelemetry provides distributed tracing and structured logging for TickerQ jobs via the OpenTelemetry ActivitySource API. It focuses solely on emitting TickerQ-specific telemetry; exporters, sampling, and backends are configured in your application's OpenTelemetry pipeline.
Quick Start
using TickerQ.DependencyInjection;
using TickerQ.Instrumentation.OpenTelemetry;
using OpenTelemetry.Trace;
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource("TickerQ")
.AddConsoleExporter()
.AddJaegerExporter();
});
// Add TickerQ with OpenTelemetry instrumentation
builder.Services.AddTickerQ(options =>
{
// Your TickerQ configuration
options.AddOpenTelemetryInstrumentation(); // 👈 Enable tracing
});
var app = builder.Build();
app.UseTickerQ();
app.Run();Trace Structure
Activity Hierarchy
TickerQ creates the following activity structure:
tickerq.job.execute.timeticker (main job execution span)
├── tickerq.job.enqueued (when job starts execution)
├── tickerq.job.completed (on successful completion)
├── tickerq.job.failed (on failure)
├── tickerq.job.cancelled (on cancellation)
├── tickerq.job.skipped (when skipped)
├── tickerq.seeding.started (for data seeding)
└── tickerq.seeding.completed (seeding completion)Activity Names
tickerq.job.execute.timeticker- Main TimeTicker executiontickerq.job.execute.crontickeroccurrence- CronTicker occurrence executiontickerq.job.enqueued- Job enqueued eventtickerq.job.completed- Job completed eventtickerq.job.failed- Job failed eventtickerq.job.cancelled- Job cancelled eventtickerq.job.skipped- Job skipped event
Activity Tags
TickerQ adds comprehensive tags to activities:
| Tag | Description | Example |
|---|---|---|
tickerq.job.id | Unique job identifier | 123e4567-e89b-12d3-a456-426614174000 |
tickerq.job.type | Type of ticker | TimeTicker, CronTicker |
tickerq.job.function | Function name | ProcessEmails |
tickerq.job.priority | Job priority | Normal, High, LongRunning |
tickerq.job.machine | Machine executing job | web-server-01 |
tickerq.job.parent_id | Parent job ID | parent-job-guid |
tickerq.job.enqueued_from | Where job was enqueued | UserController.CreateUser (Program.cs:42) |
tickerq.job.is_due | Whether job is due | true, false |
tickerq.job.is_child | Whether this is a child job | true, false |
tickerq.job.retries | Maximum retry attempts | 3 |
tickerq.job.current_attempt | Current retry attempt | 1, 2, 3 |
tickerq.job.final_status | Final execution status | Done, Failed, Cancelled |
tickerq.job.final_retry_count | Final retry count | 2 |
tickerq.job.execution_time_ms | Execution time in milliseconds | 1250 |
tickerq.job.success | Whether execution was successful | true, false |
tickerq.job.error_type | Exception type | SqlException, TimeoutException |
tickerq.job.error_message | Error message | Connection timeout |
tickerq.job.error_stack_trace | Full stack trace | at MyService.ProcessData()... |
tickerq.job.cancellation_reason | Reason for cancellation | Task was cancelled |
tickerq.job.skip_reason | Reason for skipping | Another instance is already running |
Integration Examples
With Jaeger
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource("TickerQ")
.AddJaegerExporter(options =>
{
options.Endpoint = new Uri("http://localhost:14268/api/traces");
});
});
builder.Services.AddTickerQ(options =>
{
options.AddOpenTelemetryInstrumentation();
});With Azure Application Insights
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource("TickerQ")
.AddAzureMonitorTraceExporter(options =>
{
options.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"];
});
});
builder.Services.AddTickerQ(options =>
{
options.AddOpenTelemetryInstrumentation();
});With Zipkin
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource("TickerQ")
.AddZipkinExporter(options =>
{
options.Endpoint = new Uri("http://localhost:9411/api/v2/spans");
});
});
builder.Services.AddTickerQ(options =>
{
options.AddOpenTelemetryInstrumentation();
});With OTLP (OpenTelemetry Protocol)
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource("TickerQ")
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://localhost:4317");
});
});
builder.Services.AddTickerQ(options =>
{
options.AddOpenTelemetryInstrumentation();
});Structured Logging
TickerQ OpenTelemetry integration provides structured logging through ILogger:
Log Output Examples
[INF] TickerQ Job enqueued: TimeTicker - ProcessEmails (123e4567-e89b-12d3-a456-426614174000) from ExecutionTaskHandler
[INF] TickerQ Job completed: ProcessEmails (123e4567-e89b-12d3-a456-426614174000) in 1250ms - Success: True
[ERR] TickerQ Job failed: ProcessEmails (123e4567-e89b-12d3-a456-426614174000) - Retry 1 - Connection timeout
[INF] TickerQ Job completed: ProcessEmails (123e4567-e89b-12d3-a456-426614174000) in 2500ms - Success: False
[WRN] TickerQ Job cancelled: ProcessEmails (123e4567-e89b-12d3-a456-426614174000) - Task was cancelled
[INF] TickerQ Job skipped: ProcessEmails (123e4567-e89b-12d3-a456-426614174000) - Another CronOccurrence is already running!
[INF] TickerQ start seeding data: TimeTicker (production-node-01)
[INF] TickerQ completed seeding data: TimeTicker (production-node-01)Logging Frameworks
Works with any logging framework that integrates with ILogger:
Serilog
builder.Host.UseSerilog((context, config) =>
{
config.WriteTo.Console()
.WriteTo.File("logs/tickerq-.txt", rollingInterval: RollingInterval.Day)
.Enrich.FromLogContext();
});NLog
builder.Logging.ClearProviders();
builder.Logging.AddNLog();Application Insights
builder.Services.AddApplicationInsightsTelemetry();Parent-Child Relationships
TickerQ maintains trace relationships between parent and child jobs:
tickerq.job.execute.timeticker (parent)
└── tickerq.job.execute.timeticker (child)
└── tickerq.job.execute.timeticker (grandchild)Child job traces are linked to parent traces using the tickerq.job.parent_id tag.
Performance Considerations
Minimal Overhead
- Activities are only created when OpenTelemetry listeners are active
- Uses structured logging with minimal string allocations
- No performance impact when tracing is disabled
Conditional Tracing
Tracer automatically handles cases where no listeners are registered:
// No overhead if no listeners
using var activity = _instrumentation.StartJobActivity("my-job", context);
// Activity will be null if no listenersFiltering and Sampling
Configure sampling for TickerQ traces:
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource("TickerQ")
.SetSampler(new TraceIdRatioBasedSampler(0.1)) // Sample 10% of traces
.AddJaegerExporter();
});Or filter specific jobs:
tracing.AddSource("TickerQ")
.AddProcessor(new SimpleActivityExportProcessor(new CustomExporter()))
.AddJaegerExporter();Best Practices
1. Correlation with Application Traces
Ensure TickerQ traces are correlated with your application traces:
// In your job function
using var activity = Activity.Current; // Get current activity
if (activity != null)
{
activity.SetTag("custom.tag", "value");
}2. Filter High-Volume Jobs
Consider sampling or filtering for high-frequency jobs:
// Sample only 1% of high-frequency jobs
tracing.SetSampler(new TraceIdRatioBasedSampler(0.01));3. Use Structured Logging
Leverage structured logging for better querying:
_logger.LogInformation(
"Job {JobId} completed in {ElapsedMs}ms with status {Status}",
jobId, elapsedMs, status);4. Monitor Trace Volume
Monitor trace volume in your observability platform to avoid overwhelming your tracing backend.
Requirements
- .NET 8.0 or later
- OpenTelemetry 1.7.0 or later
- TickerQ.Utilities (automatically included)
