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.



521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
# File 'lib/temporalio/contrib/open_telemetry.rb', line 521

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.



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
# File 'lib/temporalio/contrib/open_telemetry.rb', line 480

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