blob: c08858be234666696cf5c9e251f0388bf33a7ebf [file] [log] [blame]
/*!
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { performance } from 'perf_hooks';
import config from '../config/AgentConfig';
import { ContextCarrier } from '../trace/context/ContextCarrier';
import ContextManager from '../trace/context/ContextManager';
import { Component } from '../trace/Component';
import Tag from '../Tag';
import Span from '../trace/span/Span';
import { SpanLayer } from '../proto/language-agent/Tracing_pb';
import { default as agent } from '../index';
let _lastTimestamp = -Infinity;
const KeyTrace = '__revdTraceId';
const KeyParams = '__revdParams'; // original params (if not originally an object)
class AWSLambdaTriggerPlugin {
// default working start function, should be overridden by the various types of lambda trigger subclasses
start(event: any, context: any): [Span, any] {
let peer = 'Unknown';
let carrier: ContextCarrier | undefined = undefined;
if (event && typeof event === 'object') {
// pull traceid out of params if it is in there
let traceId = event[KeyTrace];
if (traceId && typeof traceId === 'string') {
const idx = traceId.lastIndexOf('/');
if (idx !== -1) {
peer = traceId.slice(idx + 1);
traceId = traceId.slice(0, idx);
carrier = ContextCarrier.from({ sw8: traceId });
if (carrier) {
if (!carrier.isValid()) carrier = undefined;
else {
const params = event[KeyParams];
if (params !== undefined) event = params;
else delete event[KeyTrace];
}
}
}
}
}
const span = ContextManager.current.newEntrySpan('AWS/Lambda/' + (context.functionName || ''), carrier);
span.component = Component.AWSLAMBDA_FUNCTION;
span.peer = peer;
span.start();
return [span, event];
}
// default working stop function
stop(span: Span, err: Error | null, res: any): void {
span.stop();
}
wrap(func: any) {
return async (event: any, context: any, callback: any) => {
const ts = performance.now() / 1000;
let done = async (err: Error | null, res?: any) => {
done = async (err: Error | null, res?: any) => res;
if (err) span.error(err);
this.stop(span, err, res);
if (config.awsLambdaFlush >= 0) {
if (ts - _lastTimestamp >= config.awsLambdaFlush) {
await new Promise((resolve) => setTimeout(resolve, 0)); // child spans of this span may have finalization waiting in the event loop in which case we give them a chance to run so that the segment can be archived properly for flushing
const p = agent.flush(); // flush all data before aws freezes the process on exit
if (p) await p;
}
_lastTimestamp = performance.now() / 1000;
}
return res;
};
let cbdone = (err: Error | null, res?: any): any => {
// for the weird AWS done function behaviors
cbdone = (err: Error | null, res?: any) => ({ finally: () => undefined });
return done(err, res);
};
ContextManager.clearAll(); // need this because AWS seems to chain sequential independent operations linearly instead of hierarchically
const _done = context.done;
const [span, _event] = this.start(event, context);
try {
event = _event;
span.layer = SpanLayer.HTTP;
if (context.invokedFunctionArn) span.tag(Tag.arn(context.invokedFunctionArn));
context.done = (err: Error | null, res: any) => {
cbdone(err, res).finally(() => _done(err, res));
};
context.succeed = (res: any) => {
cbdone(null, res).finally(() => _done(null, res));
};
context.fail = (err: Error | null) => {
cbdone(err).finally(() => _done(err));
};
let ret = func(event, context, (err: Error | null, res: any) => {
cbdone(err, res).finally(() => callback(err, res));
});
if (typeof ret?.then === 'function')
// generic Promise check
ret = await ret;
return await done(null, ret);
} catch (e) {
await done(e, null);
throw e;
}
};
}
}
// noinspection JSUnusedGlobalSymbols
export default new AWSLambdaTriggerPlugin();
export { AWSLambdaTriggerPlugin, KeyTrace, KeyParams };