Module: Temporalio::Contrib::OpenTelemetry::Workflow

Defined in:
lib/temporalio/contrib/open_telemetry.rb

Overview

Contains workflow methods that can be used for OpenTelemetry.

Class Method Summary collapse

Class Method Details

.completed_span(name, attributes: {}, links: nil, kind: nil, exception: nil, even_during_replay: false) ⇒ OpenTelemetry::Trace::Span?

Note:

WARNING: It is UNSAFE to rely on the result of this method as it may be different/absent on replay.

Create a completed span only if not replaying (or ‘even_during_replay` is true).

Parameters:

  • name (String)

    Span name.

  • attributes (Hash) (defaults to: {})

    Span attributes. These will have workflow and run ID automatically added.

  • links (Array, nil) (defaults to: nil)

    Span links.

  • kind (Symbol, nil) (defaults to: nil)

    Span kind.

  • exception (Exception, nil) (defaults to: nil)

    Exception to record on the span.

  • even_during_replay (Boolean) (defaults to: false)

    Set to true to record this span even during replay. Most users should never set this.

Returns:

  • (OpenTelemetry::Trace::Span, nil)

    Span if one was created. WARNING: It is UNSAFE to use this value.



475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
# File 'lib/temporalio/contrib/open_telemetry.rb', line 475

def self.completed_span(
  name,
  attributes: {},
  links: nil,
  kind: nil,
  exception: nil,
  even_during_replay: false
)
  # Get root interceptor, which also checks if in workflow
  root = Temporalio::Workflow.storage[:__temporal_opentelemetry_tracing_interceptor] #: TracingInterceptor?
  raise 'Tracing interceptor not configured' unless root

  # Do nothing if replaying and not wanted during replay
  return nil if !even_during_replay && Temporalio::Workflow::Unsafe.replaying?

  # Create attributes, adding user-defined ones
  attributes = { 'temporalWorkflowID' => Temporalio::Workflow.info.workflow_id,
                 'temporalRunID' => Temporalio::Workflow.info.run_id }.merge(attributes)

  time = Temporalio::Workflow.now.dup
  # Disable durable scheduler because 1) synchronous/non-batch span processors in OTel use network (though could
  # have just used Unsafe.io_enabled for this if not for the next point) and 2) OTel uses Ruby Timeout which we
  # don't want to use durable timers.
  Temporalio::Workflow::Unsafe.durable_scheduler_disabled do
    # If there is no span on the context and the user hasn't opted in to always creating, do not create. This
    # prevents orphans if there was no span originally created from the client start-workflow call.
    if ::OpenTelemetry::Trace.current_span == ::OpenTelemetry::Trace::Span::INVALID &&
       !root._always_create_workflow_spans
      return nil
    end

    span = root.tracer.start_span(name, attributes:, links:, start_timestamp: time, kind:) # steep:ignore
    # Record exception and set status if present
    if exception
      span.record_exception(exception)
      # Only set the status if it is not a benign application error
      unless exception.is_a?(Error::ApplicationError) &&
             exception.category == Error::ApplicationError::Category::BENIGN
        span.status = ::OpenTelemetry::Trace::Status.error("Unhandled exception of type: #{exception.class}")
      end
    end
    # Finish the span (returns self)
    span.finish(end_timestamp: time)
  end
end

.with_completed_span(name, attributes: {}, links: nil, kind: nil, exception: nil, even_during_replay: false) { ... } ⇒ Object

Create a completed span and execute block with the span set on the context.

Parameters:

  • name (String)

    Span name.

  • attributes (Hash) (defaults to: {})

    Span attributes. These will have workflow and run ID automatically added.

  • links (Array, nil) (defaults to: nil)

    Span links.

  • kind (Symbol, nil) (defaults to: nil)

    Span kind.

  • exception (Exception, nil) (defaults to: nil)

    Exception to record on the span.

  • even_during_replay (Boolean) (defaults to: false)

    Set to true to record this span even during replay. Most users should never set this.

Yields:

  • Block to call. It is UNSAFE to expect any parameters in this block.

Returns:

  • (Object)

    Result of the block.



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/temporalio/contrib/open_telemetry.rb', line 434

def self.with_completed_span(
  name,
  attributes: {},
  links: nil,
  kind: nil,
  exception: nil,
  even_during_replay: false
)
  span = completed_span(name, attributes:, links:, kind:, exception:, even_during_replay:)
  if span
    # We cannot use ::OpenTelemetry::Trace.with_span here unfortunately. We need to disable the durable
    # scheduler for just the span attach/detach but leave it enabled for the user code (see
    # WorkflowInbound#_attach_current for why we have to disable scheduler even for these simple operations).
    token = Temporalio::Workflow::Unsafe.durable_scheduler_disabled do
      ::OpenTelemetry::Context.attach(::OpenTelemetry::Trace.context_with_span(span))
    end
    begin
      # Yield with no parameters
      yield
    ensure
      Temporalio::Workflow::Unsafe.durable_scheduler_disabled do
        ::OpenTelemetry::Context.detach(token)
      end
    end
  else
    yield
  end
end