Skip to content

Commit

Permalink
Update functionality
Browse files Browse the repository at this point in the history
now that azure changes have rolled out.

and add a few little comments
  • Loading branch information
ejizba committed May 16, 2024
1 parent bcf778a commit d23b87c
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 33 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion plugins/node/instrumentation-azure-functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@opentelemetry/api": "^1.3.0"
},
"devDependencies": {
"@azure/functions": "^4.0.0",
"@azure/functions": "^4.5.0",
"@opentelemetry/api": "^1.3.0",
"@opentelemetry/contrib-test-utils": "^0.39.0",
"@types/mocha": "7.0.2",
Expand Down
92 changes: 71 additions & 21 deletions plugins/node/instrumentation-azure-functions/src/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
* limitations under the License.
*/

import type * as AzureFunctions from '@azure/functions';
import type { Disposable } from '@azure/functions';
import type * as AzFunc from '@azure/functions';
import { context as otelContext, propagation } from '@opentelemetry/api';
import { SeverityNumber } from '@opentelemetry/api-logs';
import {
InstrumentationBase,
InstrumentationConfig,
Expand All @@ -25,7 +25,7 @@ import {
import { VERSION } from './version';

export class AzureFunctionsInstrumentation extends InstrumentationBase {
private _funcDisposable: Disposable | undefined;
private _azFuncDisposable: AzFunc.Disposable | undefined;

constructor(config: InstrumentationConfig = {}) {
super('@opentelemetry/instrumentation-azure-functions', VERSION, config);
Expand All @@ -34,30 +34,80 @@ export class AzureFunctionsInstrumentation extends InstrumentationBase {
protected init() {
return new InstrumentationNodeModuleDefinition(
'@azure/functions',
['^4.0.0'],
(moduleExports: typeof AzureFunctions) => this._patch(moduleExports),
() => this._unPatch()
['^4.5.0'],
(moduleExports: typeof AzFunc) => this._patch(moduleExports),
(moduleExports: typeof AzFunc) => this._unPatch(moduleExports)
);
}

private _patch(func: typeof AzureFunctions): typeof AzureFunctions {
this._funcDisposable = func.app.hook.preInvocation(context => {
const traceContext = context.invocationContext.traceContext;
if (traceContext) {
context.functionHandler = otelContext.bind(
propagation.extract(otelContext.active(), {
traceparent: traceContext.traceParent,
tracestate: traceContext.traceState,
}),
context.functionHandler
);
}
private _patch(azFunc: typeof AzFunc): typeof AzFunc {
const disposables: AzFunc.Disposable[] = [];

// Tell the Azure Functions Host that we will send logs directly from Node.js
// (so that the host doesn't duplicate)
azFunc.app.setup({
capabilities: {
WorkerOpenTelemetryEnabled: true,
},
});

return func;
// Send logs directly from Node.js
disposables.push(
azFunc.app.hook.log(context => {
this.logger.emit({
body: context.message,
severityNumber: toOtelSeverityNumber(context.level),
severityText: context.level,
});
})
);

// Ensure Azure Functions Host trace context is propagated onto the user's Node.js function handler
disposables.push(
azFunc.app.hook.preInvocation(context => {
const traceContext = context.invocationContext.traceContext;
if (traceContext) {
context.functionHandler = otelContext.bind(
propagation.extract(otelContext.active(), {
traceparent: traceContext.traceParent,
tracestate: traceContext.traceState,
}),
context.functionHandler
);
}
})
);

this._azFuncDisposable = azFunc.Disposable.from(...disposables);
return azFunc;
}

private _unPatch(): void {
this._funcDisposable?.dispose();
private _unPatch(azFunc: typeof AzFunc): void {
this._azFuncDisposable?.dispose();

azFunc.app.setup({
capabilities: {
WorkerOpenTelemetryEnabled: false,
},
});
}
}

function toOtelSeverityNumber(level: AzFunc.LogLevel): SeverityNumber {
switch (level) {
case 'information':
return SeverityNumber.INFO;
case 'debug':
return SeverityNumber.DEBUG;
case 'error':
return SeverityNumber.ERROR;
case 'trace':
return SeverityNumber.TRACE;
case 'warning':
return SeverityNumber.WARN;
case 'critical':
return SeverityNumber.FATAL;
default:
return SeverityNumber.UNSPECIFIED;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,57 @@
* limitations under the License.
*/

import { trace } from '@opentelemetry/api';
import { logs, SeverityNumber } from '@opentelemetry/api-logs';
import {
getTestSpans,
registerInstrumentationTesting,
} from '@opentelemetry/contrib-test-utils';
import {
InMemoryLogRecordExporter,
LoggerProvider,
SimpleLogRecordProcessor,
} from '@opentelemetry/sdk-logs';
import { AzureFunctionsInstrumentation } from '../src';

const instrumentation = registerInstrumentationTesting(
new AzureFunctionsInstrumentation()
);

import * as assert from 'assert';
import * as sinon from 'sinon';
import { trace } from '@opentelemetry/api';
const loggerProvider = new LoggerProvider();
const memoryLogExporter = new InMemoryLogRecordExporter();
loggerProvider.addLogRecordProcessor(
new SimpleLogRecordProcessor(memoryLogExporter)
);
instrumentation.setLoggerProvider(loggerProvider);
logs.setGlobalLoggerProvider(loggerProvider);

import {
HttpRequest,
InvocationContext,
LogHookHandler,
LogLevel,
PreInvocationContext,
PreInvocationHandler,
TraceContext,
app,
} from '@azure/functions';
import * as assert from 'assert';
import * as sinon from 'sinon';

describe('Azure Functions', () => {
const preInvocHookStub = sinon.stub(app.hook, 'preInvocation');
const logHookStub = sinon.stub(app.hook, 'log');
let preInvocationHook: PreInvocationHandler;
let logHook: LogHookHandler;

beforeEach(() => {
instrumentation.disable();
preInvocHookStub.reset();
logHookStub.reset();
instrumentation.enable();
preInvocationHook = preInvocHookStub.getCall(0).args[0];
logHook = logHookStub.getCall(0).args[0];
});

function mockHttpTrigger(traceContext?: TraceContext) {
Expand Down Expand Up @@ -91,4 +111,45 @@ describe('Azure Functions', () => {
const [span] = spans;
assert.strictEqual(span.parentSpanId, undefined);
});

it('log hook severity conversion', async () => {
const levels: LogLevel[] = [
'information',
'debug',
'error',
'trace',
'warning',
'critical',
'none',
];
for (const level of levels) {
logHook({
level,
category: 'user',
message: level,
invocationContext: undefined,
hookData: {},
});
}

const logRecords = memoryLogExporter.getFinishedLogRecords();
assert.strictEqual(logRecords.length, 7);
assert.strictEqual(logRecords[0].body, 'information');
assert.strictEqual(logRecords[0].severityNumber, SeverityNumber.INFO);
assert.strictEqual(logRecords[1].body, 'debug');
assert.strictEqual(logRecords[1].severityNumber, SeverityNumber.DEBUG);
assert.strictEqual(logRecords[2].body, 'error');
assert.strictEqual(logRecords[2].severityNumber, SeverityNumber.ERROR);
assert.strictEqual(logRecords[3].body, 'trace');
assert.strictEqual(logRecords[3].severityNumber, SeverityNumber.TRACE);
assert.strictEqual(logRecords[4].body, 'warning');
assert.strictEqual(logRecords[4].severityNumber, SeverityNumber.WARN);
assert.strictEqual(logRecords[5].body, 'critical');
assert.strictEqual(logRecords[5].severityNumber, SeverityNumber.FATAL);
assert.strictEqual(logRecords[6].body, 'none');
assert.strictEqual(
logRecords[6].severityNumber,
SeverityNumber.UNSPECIFIED
);
});
});

0 comments on commit d23b87c

Please sign in to comment.