function snsMiddlewareFactory()

in lib/instrumentation/modules/@aws-sdk/client-sns.js [23:157]


function snsMiddlewareFactory(client, agent) {
  return [
    {
      middleware: (next, context) => async (args) => {
        const ins = agent._instrumentation;
        const log = agent.logger;
        const span = ins.currSpan();
        const input = args.input;

        // W3C trace-context propagation.
        const runContext = ins.currRunContext();
        const parentSpan =
          span || runContext.currSpan() || runContext.currTransaction();
        const targetId =
          input && (input.TopicArn || input.TargetArn || input.PhoneNumber);

        if (parentSpan) {
          // Though our spec only mentions a 10-message-attribute limit for *SQS*, we'll
          // do the same limit here, because
          // https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html
          // mentions the 10-message-attribute limit for SQS subscriptions.
          input.MessageAttributes = input.MessageAttributes || {};
          const attributesCount = Object.keys(input.MessageAttributes).length;

          if (attributesCount + 2 > MAX_SNS_MESSAGE_ATTRIBUTES) {
            log.warn(
              'cannot propagate trace-context with SNS message to %s, too many MessageAttributes',
              targetId,
            );
          } else {
            parentSpan.propagateTraceContextHeaders(
              input.MessageAttributes,
              function (msgAttrs, name, value) {
                if (name.startsWith('elastic-')) {
                  return;
                }
                msgAttrs[name] = { DataType: 'String', StringValue: value };
              },
            );
          }
        }

        // Ensure there is a span from the wrapped `client.send()`.
        if (!span || !(span.type === TYPE && span.subtype === SUBTYPE)) {
          return await next(args);
        }

        const destTargets = [
          input.TopicArn && input.TopicArn.split(':').pop(),
          input.TargetArn && input.TargetArn.split(':').pop().split('/').pop(),
          input.PhoneNumber && '<PHONE_NUMBER>',
        ];
        const topicName = destTargets.filter((a) => a).join(', ');

        if (topicName) {
          span.name += ' to ' + topicName;
        }

        let err;
        let result;
        let response;
        let statusCode;
        try {
          result = await next(args);
          response = result && result.response;
          statusCode = response && response.statusCode;
        } catch (ex) {
          // Save the error for use in `finally` below, but re-throw it to
          // not impact code flow.
          err = ex;

          // This code path happens with a Publish command that returns a 404 Not Found.
          statusCode = err && err.$metadata && err.$metadata.httpStatusCode;
          throw ex;
        } finally {
          if (statusCode) {
            span._setOutcomeFromHttpStatusCode(statusCode);
          } else {
            span._setOutcomeFromErrorCapture(constants.OUTCOME_FAILURE);
          }
          if (err && (!statusCode || statusCode >= 400)) {
            agent.captureError(err, { skipOutcome: true });
          }

          // Destination context.
          const region = await client.config.region();
          const service = { name: SUBTYPE, type: TYPE };
          const destCtx = { service };

          if (context[elasticAPMStash]) {
            destCtx.address = context[elasticAPMStash].hostname;
            destCtx.port = context[elasticAPMStash].port;
          }

          if (region) {
            destCtx.cloud = { region };
          }
          span._setDestinationContext(destCtx);

          // Message context
          if (topicName) {
            span.setMessageContext({ queue: { name: topicName } });
          }

          span.end();
        }

        return result;
      },
      options: { step: 'initialize', priority: 'high', name: 'elasticAPMSpan' },
    },
    {
      middleware: (next, context) => async (args) => {
        const req = args.request;
        let port = req.port;

        // Resolve port for HTTP(S) protocols
        if (port === undefined) {
          if (req.protocol === 'https:') {
            port = 443;
          } else if (req.protocol === 'http:') {
            port = 80;
          }
        }

        context[elasticAPMStash] = {
          hostname: req.hostname,
          port,
        };
        return next(args);
      },
      options: { step: 'finalizeRequest', name: 'elasticAPMHTTPInfo' },
    },
  ];
}