Logs SDK (#3549)
* feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * fix compile errors * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): sdk-logs init * feat(sdk-logs): add browser test config * feat: add test-utils compatible assert.rejects * feat(sdk-logs): fix writing errors in README * feat(sdk-logs): update version to 0.36.1 * feat(sdk-logs): add examples * feat(sdk-logs): fix LogRecord default timestamp to Date.now() * feat(sdk-logs): logRecord support rewrite time/body/severityNumber/newSeverityText * feat(sdk-logs): add logs processor environments * feat(sdk-logs): modify export style * feat(sdk-logs): update version to 0.36.1 * feat(sdk-logs): remove exporter factory * feat(sdk-logs): update CHANGELOG * feat(sdk-logs): change the processing of schemeUrl * feat(sdk-logs): split LoggerProviderConfig and LoggerConfig * feat(sdk-logs): getLogger with default name when name is invalid * feat(sdk-logs): improve the shutdown logic of LoggerProvider * feat(sdk-logs): improve the shutdown logic of LoggerProvider * feat(sdk-logs): make log record read-only after it has been emitted * feat(sdk-logs): logger option support includeTraceContext & LogRecordProcessor onEmit suport context * feat(sdk-logs): update version * feat(sdk-logs): update version * feat(sdk-logs): update logs example with typescript * feat(sdk-logs): update peerDependencies * feat(sdk-logs): peer-api-check support @opentelemetry/api-logs * feat(sdk-logs): update peerDependencies --------- Co-authored-by: Martin Kuba <martin@martinkuba.com> Co-authored-by: Daniel Dyla <dyladan@users.noreply.github.com>
This commit is contained in:
parent
26dfc70841
commit
a31b38a845
|
@ -8,6 +8,8 @@ logs
|
|||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
# Filter Logs singal files
|
||||
!experimental/examples/logs
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
|
|
|
@ -12,6 +12,7 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/
|
|||
### :rocket: (Enhancement)
|
||||
|
||||
* feat(tracing): log span name and IDs when span end is called multiple times [#3716](https://github.com/open-telemetry/opentelemetry-js/pull/3716)
|
||||
* feat(core): add logs environment variables; add timeout utils method. [#3549](https://github.com/open-telemetry/opentelemetry-js/pull/3549/) @fuaiyi
|
||||
|
||||
### :bug: (Bug Fix)
|
||||
|
||||
|
|
|
@ -11,6 +11,9 @@ All notable changes to experimental packages in this project will be documented
|
|||
|
||||
### :rocket: (Enhancement)
|
||||
|
||||
* feat(api-logs): 1.`LogRecord` fields update: `traceFlags`/`traceId`/`spanId` -> `context`; 2.`Logger` supports configuring `includeTraceContext`; 3.The `onEmit` method of `LogRecordProcessor` supports the `context` field. [#3549](https://github.com/open-telemetry/opentelemetry-js/pull/3549/) @fuaiyi
|
||||
* feat(sdk-logs): logs sdk implementation. [#3549](https://github.com/open-telemetry/opentelemetry-js/pull/3549/) @fuaiyi
|
||||
|
||||
### :bug: (Bug Fix)
|
||||
|
||||
* fix(sdk-node): only set DiagConsoleLogger when OTEL_LOG_LEVEL is set [#3693](https://github.com/open-telemetry/opentelemetry-js/pull/3672) @pichlermarc
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
## Installation
|
||||
|
||||
```sh
|
||||
# from this directory
|
||||
npm install
|
||||
```
|
||||
|
||||
## Run the Application
|
||||
|
||||
LogRecord
|
||||
|
||||
```sh
|
||||
npm start
|
||||
```
|
||||
|
||||
## Useful links
|
||||
|
||||
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
|
||||
- For more information on OpenTelemetry logs, visit: <https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/sdk-logs>
|
||||
|
||||
## LICENSE
|
||||
|
||||
Apache License 2.0
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';
|
||||
import { logs, SeverityNumber } from '@opentelemetry/api-logs';
|
||||
import {
|
||||
LoggerProvider,
|
||||
ConsoleLogRecordExporter,
|
||||
SimpleLogRecordProcessor,
|
||||
} from '@opentelemetry/sdk-logs';
|
||||
|
||||
// Optional and only needed to see the internal diagnostic logging (during development)
|
||||
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
|
||||
|
||||
const loggerProvider = new LoggerProvider();
|
||||
loggerProvider.addLogRecordProcessor(
|
||||
new SimpleLogRecordProcessor(new ConsoleLogRecordExporter())
|
||||
);
|
||||
|
||||
logs.setGlobalLoggerProvider(loggerProvider);
|
||||
|
||||
const logger = logs.getLogger('example', '1.0.0');
|
||||
|
||||
// emit a log record
|
||||
logger.emit({
|
||||
severityNumber: SeverityNumber.INFO,
|
||||
severityText: 'INFO',
|
||||
body: 'this is a log record body',
|
||||
attributes: { 'log.type': 'custom' },
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "logs-example",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "ts-node index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentelemetry/api": "^1.4.1",
|
||||
"@opentelemetry/api-logs": "^0.37.0",
|
||||
"@opentelemetry/sdk-logs": "^0.37.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.9.1",
|
||||
"@types/node": "18.6.5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "build",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["./index.ts"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../api"
|
||||
},
|
||||
{
|
||||
"path": "../../../experimental/packages/api-logs"
|
||||
},
|
||||
{
|
||||
"path": "../../../experimental/packages/sdk-logs"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -20,3 +20,5 @@ import { LogRecord } from './types/LogRecord';
|
|||
export class NoopLogger implements Logger {
|
||||
emit(_logRecord: LogRecord): void {}
|
||||
}
|
||||
|
||||
export const NOOP_LOGGER = new NoopLogger();
|
||||
|
|
|
@ -18,6 +18,8 @@ export * from './types/Logger';
|
|||
export * from './types/LoggerProvider';
|
||||
export * from './types/LogRecord';
|
||||
export * from './types/LoggerOptions';
|
||||
export * from './NoopLogger';
|
||||
export * from './NoopLoggerProvider';
|
||||
|
||||
import { LogsAPI } from './api/logs';
|
||||
export const logs = LogsAPI.getInstance();
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Attributes } from '@opentelemetry/api';
|
||||
import { Attributes, Context } from '@opentelemetry/api';
|
||||
|
||||
export enum SeverityNumber {
|
||||
UNSPECIFIED = 0,
|
||||
|
@ -71,17 +71,7 @@ export interface LogRecord {
|
|||
attributes?: Attributes;
|
||||
|
||||
/**
|
||||
* 8 least significant bits are the trace flags as defined in W3C Trace Context specification.
|
||||
* The Context associated with the LogRecord.
|
||||
*/
|
||||
traceFlags?: number;
|
||||
|
||||
/**
|
||||
* A unique identifier for a trace.
|
||||
*/
|
||||
traceId?: string;
|
||||
|
||||
/**
|
||||
* A unique identifier for a span within a trace.
|
||||
*/
|
||||
spanId?: string;
|
||||
context?: Context;
|
||||
}
|
||||
|
|
|
@ -27,4 +27,10 @@ export interface LoggerOptions {
|
|||
* The instrumentation scope attributes to associate with emitted telemetry
|
||||
*/
|
||||
scopeAttributes?: Attributes;
|
||||
|
||||
/**
|
||||
* Specifies whether the Trace Context should automatically be passed on to the LogRecords emitted by the Logger.
|
||||
* @default true
|
||||
*/
|
||||
includeTraceContext?: boolean;
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
build
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
mocha: true,
|
||||
node: true,
|
||||
},
|
||||
...require('../../../eslint.config.js'),
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
/bin
|
||||
/coverage
|
||||
/doc
|
||||
/test
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed 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.
|
|
@ -0,0 +1,79 @@
|
|||
# OpenTelemetry Logs SDK
|
||||
|
||||
[![NPM Published Version][npm-img]][npm-url]
|
||||
[![Apache License][license-image]][license-image]
|
||||
|
||||
**Note: This is an experimental package under active development. New releases may include breaking changes.**
|
||||
|
||||
OpenTelemetry logs module contains the foundation for all logs SDKs of [opentelemetry-js](https://github.com/open-telemetry/opentelemetry-js).
|
||||
|
||||
Used standalone, this module provides methods for manual instrumentation of code, offering full control over recording logs for client-side JavaScript (browser) and Node.js.
|
||||
|
||||
It does **not** provide automated instrumentation of known libraries or host environment logs out-of-the-box.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install --save @opentelemetry/api-logs
|
||||
npm install --save @opentelemetry/sdk-logs
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The basic setup of the SDK can be seen as followings:
|
||||
|
||||
```js
|
||||
const logsAPI = require('@opentelemetry/api-logs');
|
||||
const {
|
||||
LoggerProvider,
|
||||
SimpleLogRecordProcessor,
|
||||
ConsoleLogRecordExporter,
|
||||
} = require('@opentelemetry/sdk-logs');
|
||||
|
||||
// To start a logger, you first need to initialize the Logger provider.
|
||||
const loggerProvider = new LoggerProvider();
|
||||
// Add a processor to export log record
|
||||
loggerProvider.addLogRecordProcessor(
|
||||
new SimpleLogRecordProcessor(new ConsoleLogRecordExporter())
|
||||
);
|
||||
|
||||
// To create a log record, you first need to get a Logger instance
|
||||
const logger = loggerProvider.getLogger('default');
|
||||
|
||||
// You can also use global singleton
|
||||
logsAPI.logs.setGlobalLoggerProvider(loggerProvider);
|
||||
const logger = logsAPI.logs.getLogger('default');
|
||||
|
||||
// emit a log record
|
||||
logger.emit({
|
||||
severityNumber: SeverityNumber.INFO,
|
||||
severityText: 'INFO',
|
||||
body: 'this is a log record body',
|
||||
attributes: { 'log.type': 'LogRecord' },
|
||||
});
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
Logs configuration is a merge of both the user supplied configuration and the default
|
||||
configuration as specified in [config.ts](./src/config.ts)
|
||||
|
||||
## Example
|
||||
|
||||
See [examples/logs](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/examples/logs)
|
||||
|
||||
## Useful links
|
||||
|
||||
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
|
||||
- For more about OpenTelemetry JavaScript: <https://github.com/open-telemetry/opentelemetry-js>
|
||||
- For help or feedback on this project, join us in [GitHub Discussions][discussions-url]
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 - See [LICENSE][license-url] for more information.
|
||||
|
||||
[discussions-url]: https://github.com/open-telemetry/opentelemetry-js/discussions
|
||||
[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/main/LICENSE
|
||||
[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat
|
||||
[npm-url]: https://www.npmjs.com/package/@opentelemetry/sdk-logs
|
||||
[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fsdk%2Dlogs.svg
|
|
@ -0,0 +1,26 @@
|
|||
/*!
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
const karmaWebpackConfig = require('../../../karma.webpack');
|
||||
const karmaBaseConfig = require('../../../karma.base');
|
||||
|
||||
module.exports = config => {
|
||||
config.set(
|
||||
Object.assign({}, karmaBaseConfig, {
|
||||
webpack: karmaWebpackConfig,
|
||||
})
|
||||
);
|
||||
};
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"name": "@opentelemetry/sdk-logs",
|
||||
"version": "0.37.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"description": "OpenTelemetry logs SDK",
|
||||
"author": "OpenTelemetry Authors",
|
||||
"homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/sdk-logs",
|
||||
"license": "Apache-2.0",
|
||||
"main": "build/src/index.js",
|
||||
"module": "build/esm/index.js",
|
||||
"esnext": "build/esnext/index.js",
|
||||
"types": "build/src/index.d.ts",
|
||||
"browser": {
|
||||
"./src/platform/index.ts": "./src/platform/browser/index.ts",
|
||||
"./build/esm/platform/index.js": "./build/esm/platform/browser/index.js",
|
||||
"./build/esnext/platform/index.js": "./build/esnext/platform/browser/index.js",
|
||||
"./build/src/platform/index.js": "./build/src/platform/browser/index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/open-telemetry/opentelemetry-js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/open-telemetry/opentelemetry-js/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm run compile",
|
||||
"compile": "tsc --build tsconfig.json tsconfig.esm.json tsconfig.esnext.json",
|
||||
"clean": "tsc --build --clean tsconfig.json tsconfig.esm.json tsconfig.esnext.json",
|
||||
"test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts'",
|
||||
"test:browser": "nyc karma start --single-run",
|
||||
"tdd": "npm run test -- --watch-extensions ts --watch",
|
||||
"tdd:browser": "karma start",
|
||||
"codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../../",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"lint:fix": "eslint . --ext .ts --fix",
|
||||
"version": "node ../../../scripts/version-update.js",
|
||||
"watch": "tsc --build --watch tsconfig.json tsconfig.esm.json tsconfig.esnext.json",
|
||||
"precompile": "lerna run version --scope $(npm pkg get name) --include-dependencies",
|
||||
"prewatch": "node ../../../scripts/version-update.js",
|
||||
"peer-api-check": "node ../../../scripts/peer-api-check.js"
|
||||
},
|
||||
"keywords": [
|
||||
"opentelemetry",
|
||||
"nodejs",
|
||||
"logs",
|
||||
"stats",
|
||||
"profiling"
|
||||
],
|
||||
"files": [
|
||||
"build/esm/**/*.js",
|
||||
"build/esm/**/*.js.map",
|
||||
"build/esm/**/*.d.ts",
|
||||
"build/esnext/**/*.js",
|
||||
"build/esnext/**/*.js.map",
|
||||
"build/esnext/**/*.d.ts",
|
||||
"build/src/**/*.js",
|
||||
"build/src/**/*.js.map",
|
||||
"build/src/**/*.d.ts",
|
||||
"doc",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": ">=1.4.0 <1.5.0",
|
||||
"@opentelemetry/api-logs": ">=0.37.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "10.0.0",
|
||||
"@types/node": "18.6.5",
|
||||
"@types/sinon": "10.0.13",
|
||||
"@opentelemetry/api": ">=1.4.0 <1.5.0",
|
||||
"@opentelemetry/api-logs": ">=0.37.0",
|
||||
"codecov": "3.8.3",
|
||||
"karma": "6.3.16",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||
"karma-mocha": "2.0.1",
|
||||
"karma-spec-reporter": "0.0.32",
|
||||
"karma-webpack": "4.0.2",
|
||||
"mocha": "10.0.0",
|
||||
"nyc": "15.1.0",
|
||||
"rimraf": "3.0.2",
|
||||
"sinon": "14.0.0",
|
||||
"ts-mocha": "10.0.0",
|
||||
"typescript": "4.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "^1.11.0",
|
||||
"@opentelemetry/resources": "^1.11.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 { Attributes, AttributeValue, diag } from '@opentelemetry/api';
|
||||
import type * as logsAPI from '@opentelemetry/api-logs';
|
||||
import * as api from '@opentelemetry/api';
|
||||
import {
|
||||
timeInputToHrTime,
|
||||
isAttributeValue,
|
||||
InstrumentationScope,
|
||||
} from '@opentelemetry/core';
|
||||
import type { IResource } from '@opentelemetry/resources';
|
||||
|
||||
import type { ReadableLogRecord } from './export/ReadableLogRecord';
|
||||
import type { LogRecordLimits } from './types';
|
||||
import { Logger } from './Logger';
|
||||
|
||||
export class LogRecord implements ReadableLogRecord {
|
||||
readonly hrTime: api.HrTime;
|
||||
readonly spanContext?: api.SpanContext;
|
||||
readonly resource: IResource;
|
||||
readonly instrumentationScope: InstrumentationScope;
|
||||
readonly attributes: Attributes = {};
|
||||
private _severityText?: string;
|
||||
private _severityNumber?: logsAPI.SeverityNumber;
|
||||
private _body?: string;
|
||||
|
||||
private _isReadonly: boolean = false;
|
||||
private readonly _logRecordLimits: LogRecordLimits;
|
||||
|
||||
set severityText(severityText: string | undefined) {
|
||||
if (this._isLogRecordReadonly()) {
|
||||
return;
|
||||
}
|
||||
this._severityText = severityText;
|
||||
}
|
||||
get severityText(): string | undefined {
|
||||
return this._severityText;
|
||||
}
|
||||
|
||||
set severityNumber(severityNumber: logsAPI.SeverityNumber | undefined) {
|
||||
if (this._isLogRecordReadonly()) {
|
||||
return;
|
||||
}
|
||||
this._severityNumber = severityNumber;
|
||||
}
|
||||
get severityNumber(): logsAPI.SeverityNumber | undefined {
|
||||
return this._severityNumber;
|
||||
}
|
||||
|
||||
set body(body: string | undefined) {
|
||||
if (this._isLogRecordReadonly()) {
|
||||
return;
|
||||
}
|
||||
this._body = body;
|
||||
}
|
||||
get body(): string | undefined {
|
||||
return this._body;
|
||||
}
|
||||
|
||||
constructor(logger: Logger, logRecord: logsAPI.LogRecord) {
|
||||
const {
|
||||
timestamp = Date.now(),
|
||||
severityNumber,
|
||||
severityText,
|
||||
body,
|
||||
attributes = {},
|
||||
context,
|
||||
} = logRecord;
|
||||
|
||||
this.hrTime = timeInputToHrTime(timestamp);
|
||||
if (context) {
|
||||
const spanContext = api.trace.getSpanContext(context);
|
||||
if (spanContext && api.isSpanContextValid(spanContext)) {
|
||||
this.spanContext = spanContext;
|
||||
}
|
||||
}
|
||||
this.severityNumber = severityNumber;
|
||||
this.severityText = severityText;
|
||||
this.body = body;
|
||||
this.resource = logger.resource;
|
||||
this.instrumentationScope = logger.instrumentationScope;
|
||||
this._logRecordLimits = logger.getLogRecordLimits();
|
||||
this.setAttributes(attributes);
|
||||
}
|
||||
|
||||
public setAttribute(key: string, value?: AttributeValue) {
|
||||
if (this._isLogRecordReadonly()) {
|
||||
return this;
|
||||
}
|
||||
if (value === null) {
|
||||
return this;
|
||||
}
|
||||
if (key.length === 0) {
|
||||
api.diag.warn(`Invalid attribute key: ${key}`);
|
||||
return this;
|
||||
}
|
||||
if (!isAttributeValue(value)) {
|
||||
api.diag.warn(`Invalid attribute value set for key: ${key}`);
|
||||
return this;
|
||||
}
|
||||
if (
|
||||
Object.keys(this.attributes).length >=
|
||||
this._logRecordLimits.attributeCountLimit! &&
|
||||
!Object.prototype.hasOwnProperty.call(this.attributes, key)
|
||||
) {
|
||||
return this;
|
||||
}
|
||||
this.attributes[key] = this._truncateToSize(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setAttributes(attributes: Attributes) {
|
||||
for (const [k, v] of Object.entries(attributes)) {
|
||||
this.setAttribute(k, v);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public setBody(body: string) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setSeverityNumber(severityNumber: logsAPI.SeverityNumber) {
|
||||
this.severityNumber = severityNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public setSeverityText(severityText: string) {
|
||||
this.severityText = severityText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A LogRecordProcessor may freely modify logRecord for the duration of the OnEmit call.
|
||||
* If logRecord is needed after OnEmit returns (i.e. for asynchronous processing) only reads are permitted.
|
||||
*/
|
||||
public makeReadonly() {
|
||||
this._isReadonly = true;
|
||||
}
|
||||
|
||||
private _truncateToSize(value: AttributeValue): AttributeValue {
|
||||
const limit = this._logRecordLimits.attributeValueLengthLimit || 0;
|
||||
// Check limit
|
||||
if (limit <= 0) {
|
||||
// Negative values are invalid, so do not truncate
|
||||
api.diag.warn(`Attribute value limit must be positive, got ${limit}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
// String
|
||||
if (typeof value === 'string') {
|
||||
return this._truncateToLimitUtil(value, limit);
|
||||
}
|
||||
|
||||
// Array of strings
|
||||
if (Array.isArray(value)) {
|
||||
return (value as []).map(val =>
|
||||
typeof val === 'string' ? this._truncateToLimitUtil(val, limit) : val
|
||||
);
|
||||
}
|
||||
|
||||
// Other types, no need to apply value length limit
|
||||
return value;
|
||||
}
|
||||
|
||||
private _truncateToLimitUtil(value: string, limit: number): string {
|
||||
if (value.length <= limit) {
|
||||
return value;
|
||||
}
|
||||
return value.substring(0, limit);
|
||||
}
|
||||
|
||||
private _isLogRecordReadonly(): boolean {
|
||||
if (this._isReadonly) {
|
||||
diag.warn('Can not execute the operation on emitted log record');
|
||||
}
|
||||
return this._isReadonly;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 { Context } from '@opentelemetry/api';
|
||||
|
||||
import { LogRecord } from './LogRecord';
|
||||
|
||||
export interface LogRecordProcessor {
|
||||
/**
|
||||
* Forces to export all finished log records
|
||||
*/
|
||||
forceFlush(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Called when a {@link LogRecord} is emit
|
||||
* @param logRecord the ReadWriteLogRecord that just emitted.
|
||||
* @param context the current Context, or an empty Context if the Logger was obtained with include_trace_context=false
|
||||
*/
|
||||
onEmit(logRecord: LogRecord, context?: Context): void;
|
||||
|
||||
/**
|
||||
* Shuts down the processor. Called when SDK is shut down. This is an
|
||||
* opportunity for processor to do any cleanup required.
|
||||
*/
|
||||
shutdown(): Promise<void>;
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 type * as logsAPI from '@opentelemetry/api-logs';
|
||||
import type { IResource } from '@opentelemetry/resources';
|
||||
import type { InstrumentationScope } from '@opentelemetry/core';
|
||||
import { context } from '@opentelemetry/api';
|
||||
|
||||
import type { LoggerConfig, LogRecordLimits } from './types';
|
||||
import { LogRecord } from './LogRecord';
|
||||
import { LoggerProvider } from './LoggerProvider';
|
||||
import { mergeConfig } from './config';
|
||||
import { LogRecordProcessor } from './LogRecordProcessor';
|
||||
|
||||
export class Logger implements logsAPI.Logger {
|
||||
public readonly resource: IResource;
|
||||
private readonly _loggerConfig: Required<LoggerConfig>;
|
||||
|
||||
constructor(
|
||||
public readonly instrumentationScope: InstrumentationScope,
|
||||
config: LoggerConfig,
|
||||
private _loggerProvider: LoggerProvider
|
||||
) {
|
||||
this._loggerConfig = mergeConfig(config);
|
||||
this.resource = _loggerProvider.resource;
|
||||
}
|
||||
|
||||
public emit(logRecord: logsAPI.LogRecord): void {
|
||||
const currentContext = this._loggerConfig.includeTraceContext
|
||||
? context.active()
|
||||
: undefined;
|
||||
/**
|
||||
* If a Logger was obtained with include_trace_context=true,
|
||||
* the LogRecords it emits MUST automatically include the Trace Context from the active Context,
|
||||
* if Context has not been explicitly set.
|
||||
*/
|
||||
const logRecordInstance = new LogRecord(this, {
|
||||
context: currentContext,
|
||||
...logRecord,
|
||||
});
|
||||
/**
|
||||
* the explicitly passed Context,
|
||||
* the current Context, or an empty Context if the Logger was obtained with include_trace_context=false
|
||||
*/
|
||||
this.getActiveLogRecordProcessor().onEmit(
|
||||
logRecordInstance,
|
||||
currentContext
|
||||
);
|
||||
/**
|
||||
* A LogRecordProcessor may freely modify logRecord for the duration of the OnEmit call.
|
||||
* If logRecord is needed after OnEmit returns (i.e. for asynchronous processing) only reads are permitted.
|
||||
*/
|
||||
logRecordInstance.makeReadonly();
|
||||
}
|
||||
|
||||
public getLogRecordLimits(): LogRecordLimits {
|
||||
return this._loggerConfig.logRecordLimits;
|
||||
}
|
||||
|
||||
public getActiveLogRecordProcessor(): LogRecordProcessor {
|
||||
return this._loggerProvider.getActiveLogRecordProcessor();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 { diag } from '@opentelemetry/api';
|
||||
import type * as logsAPI from '@opentelemetry/api-logs';
|
||||
import { NOOP_LOGGER } from '@opentelemetry/api-logs';
|
||||
import { IResource, Resource } from '@opentelemetry/resources';
|
||||
import { BindOnceFuture, merge } from '@opentelemetry/core';
|
||||
|
||||
import type { LoggerProviderConfig } from './types';
|
||||
import type { LogRecordProcessor } from './LogRecordProcessor';
|
||||
import { Logger } from './Logger';
|
||||
import { loadDefaultConfig, reconfigureLimits } from './config';
|
||||
import { MultiLogRecordProcessor } from './MultiLogRecordProcessor';
|
||||
import { NoopLogRecordProcessor } from './export/NoopLogRecordProcessor';
|
||||
|
||||
export const DEFAULT_LOGGER_NAME = 'unknown';
|
||||
|
||||
export class LoggerProvider implements logsAPI.LoggerProvider {
|
||||
public readonly resource: IResource;
|
||||
|
||||
private readonly _loggers: Map<string, Logger> = new Map();
|
||||
private _activeProcessor: MultiLogRecordProcessor;
|
||||
private readonly _registeredLogRecordProcessors: LogRecordProcessor[] = [];
|
||||
private readonly _config: LoggerProviderConfig;
|
||||
private _shutdownOnce: BindOnceFuture<void>;
|
||||
|
||||
constructor(config: LoggerProviderConfig = {}) {
|
||||
const {
|
||||
resource = Resource.empty(),
|
||||
logRecordLimits,
|
||||
forceFlushTimeoutMillis,
|
||||
} = merge({}, loadDefaultConfig(), reconfigureLimits(config));
|
||||
this.resource = Resource.default().merge(resource);
|
||||
this._config = {
|
||||
logRecordLimits,
|
||||
resource: this.resource,
|
||||
forceFlushTimeoutMillis,
|
||||
};
|
||||
|
||||
this._shutdownOnce = new BindOnceFuture(this._shutdown, this);
|
||||
|
||||
// add a default processor: NoopLogRecordProcessor
|
||||
this._activeProcessor = new MultiLogRecordProcessor(
|
||||
[new NoopLogRecordProcessor()],
|
||||
forceFlushTimeoutMillis
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a logger with the configuration of the LoggerProvider.
|
||||
*/
|
||||
public getLogger(
|
||||
name: string,
|
||||
version?: string,
|
||||
options?: logsAPI.LoggerOptions
|
||||
): logsAPI.Logger {
|
||||
if (this._shutdownOnce.isCalled) {
|
||||
diag.warn('A shutdown LoggerProvider cannot provide a Logger');
|
||||
return NOOP_LOGGER;
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
diag.warn('Logger requested without instrumentation scope name.');
|
||||
}
|
||||
const loggerName = name || DEFAULT_LOGGER_NAME;
|
||||
const key = `${loggerName}@${version || ''}:${options?.schemaUrl || ''}`;
|
||||
if (!this._loggers.has(key)) {
|
||||
this._loggers.set(
|
||||
key,
|
||||
new Logger(
|
||||
{ name: loggerName, version, schemaUrl: options?.schemaUrl },
|
||||
{
|
||||
logRecordLimits: this._config.logRecordLimits,
|
||||
includeTraceContext: options?.includeTraceContext,
|
||||
},
|
||||
this
|
||||
)
|
||||
);
|
||||
}
|
||||
return this._loggers.get(key)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new {@link LogRecordProcessor} to this logger.
|
||||
* @param processor the new LogRecordProcessor to be added.
|
||||
*/
|
||||
public addLogRecordProcessor(processor: LogRecordProcessor) {
|
||||
if (this._registeredLogRecordProcessors.length === 0) {
|
||||
// since we might have enabled by default a batchProcessor, we disable it
|
||||
// before adding the new one
|
||||
this._activeProcessor
|
||||
.shutdown()
|
||||
.catch(err =>
|
||||
diag.error(
|
||||
'Error while trying to shutdown current log record processor',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
this._registeredLogRecordProcessors.push(processor);
|
||||
this._activeProcessor = new MultiLogRecordProcessor(
|
||||
this._registeredLogRecordProcessors,
|
||||
this._config.forceFlushTimeoutMillis!
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all registered LogRecordProcessor to flush any buffered data.
|
||||
*
|
||||
* Returns a promise which is resolved when all flushes are complete.
|
||||
*/
|
||||
public forceFlush(): Promise<void> {
|
||||
// do not flush after shutdown
|
||||
if (this._shutdownOnce.isCalled) {
|
||||
diag.warn('invalid attempt to force flush after LoggerProvider shutdown');
|
||||
return this._shutdownOnce.promise;
|
||||
}
|
||||
return this._activeProcessor.forceFlush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all buffered data and shut down the LoggerProvider and all registered
|
||||
* LogRecordProcessor.
|
||||
*
|
||||
* Returns a promise which is resolved when all flushes are complete.
|
||||
*/
|
||||
public shutdown(): Promise<void> {
|
||||
if (this._shutdownOnce.isCalled) {
|
||||
diag.warn('shutdown may only be called once per LoggerProvider');
|
||||
return this._shutdownOnce.promise;
|
||||
}
|
||||
return this._shutdownOnce.call();
|
||||
}
|
||||
|
||||
public getActiveLogRecordProcessor(): MultiLogRecordProcessor {
|
||||
return this._activeProcessor;
|
||||
}
|
||||
|
||||
public getActiveLoggers(): Map<string, Logger> {
|
||||
return this._loggers;
|
||||
}
|
||||
|
||||
private _shutdown(): Promise<void> {
|
||||
return this._activeProcessor.shutdown();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 { callWithTimeout } from '@opentelemetry/core';
|
||||
|
||||
import type { LogRecordProcessor } from './LogRecordProcessor';
|
||||
import type { LogRecord } from './LogRecord';
|
||||
|
||||
/**
|
||||
* Implementation of the {@link LogRecordProcessor} that simply forwards all
|
||||
* received events to a list of {@link LogRecordProcessor}s.
|
||||
*/
|
||||
export class MultiLogRecordProcessor implements LogRecordProcessor {
|
||||
constructor(
|
||||
public readonly processors: LogRecordProcessor[],
|
||||
public readonly forceFlushTimeoutMillis: number
|
||||
) {}
|
||||
|
||||
public async forceFlush(): Promise<void> {
|
||||
const timeout = this.forceFlushTimeoutMillis;
|
||||
await Promise.all(
|
||||
this.processors.map(processor =>
|
||||
callWithTimeout(processor.forceFlush(), timeout)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public onEmit(logRecord: LogRecord): void {
|
||||
this.processors.forEach(processors => processors.onEmit(logRecord));
|
||||
}
|
||||
|
||||
public async shutdown(): Promise<void> {
|
||||
await Promise.all(this.processors.map(processor => processor.shutdown()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 {
|
||||
DEFAULT_ATTRIBUTE_COUNT_LIMIT,
|
||||
DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT,
|
||||
getEnv,
|
||||
getEnvWithoutDefaults,
|
||||
} from '@opentelemetry/core';
|
||||
import { LoggerConfig } from './types';
|
||||
|
||||
export function loadDefaultConfig() {
|
||||
return {
|
||||
forceFlushTimeoutMillis: 30000,
|
||||
logRecordLimits: {
|
||||
attributeValueLengthLimit:
|
||||
getEnv().OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT,
|
||||
attributeCountLimit: getEnv().OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT,
|
||||
},
|
||||
includeTraceContext: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* When general limits are provided and model specific limits are not,
|
||||
* configures the model specific limits by using the values from the general ones.
|
||||
* @param userConfig User provided tracer configuration
|
||||
*/
|
||||
export function reconfigureLimits(userConfig: LoggerConfig): LoggerConfig {
|
||||
const logRecordLimits = Object.assign({}, userConfig.logRecordLimits);
|
||||
|
||||
const parsedEnvConfig = getEnvWithoutDefaults();
|
||||
|
||||
/**
|
||||
* Reassign log record attribute count limit to use first non null value defined by user or use default value
|
||||
*/
|
||||
logRecordLimits.attributeCountLimit =
|
||||
userConfig.logRecordLimits?.attributeCountLimit ??
|
||||
parsedEnvConfig.OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT ??
|
||||
parsedEnvConfig.OTEL_ATTRIBUTE_COUNT_LIMIT ??
|
||||
DEFAULT_ATTRIBUTE_COUNT_LIMIT;
|
||||
|
||||
/**
|
||||
* Reassign log record attribute value length limit to use first non null value defined by user or use default value
|
||||
*/
|
||||
logRecordLimits.attributeValueLengthLimit =
|
||||
userConfig.logRecordLimits?.attributeValueLengthLimit ??
|
||||
parsedEnvConfig.OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT ??
|
||||
parsedEnvConfig.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT ??
|
||||
DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
||||
|
||||
return Object.assign({}, userConfig, { logRecordLimits });
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to merge Default configuration (as specified in './config') with
|
||||
* user provided configurations.
|
||||
*/
|
||||
export function mergeConfig(userConfig: LoggerConfig): Required<LoggerConfig> {
|
||||
const DEFAULT_CONFIG = loadDefaultConfig();
|
||||
|
||||
const target = Object.assign({}, DEFAULT_CONFIG, userConfig);
|
||||
|
||||
target.logRecordLimits = Object.assign(
|
||||
{},
|
||||
DEFAULT_CONFIG.logRecordLimits,
|
||||
userConfig.logRecordLimits || {}
|
||||
);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
export const DEFAULT_EVENT_DOMAIN = 'default';
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 type { ExportResult } from '@opentelemetry/core';
|
||||
import { diag } from '@opentelemetry/api';
|
||||
import {
|
||||
ExportResultCode,
|
||||
getEnv,
|
||||
globalErrorHandler,
|
||||
unrefTimer,
|
||||
callWithTimeout,
|
||||
BindOnceFuture,
|
||||
} from '@opentelemetry/core';
|
||||
|
||||
import type { BufferConfig } from '../types';
|
||||
import type { LogRecord } from '../LogRecord';
|
||||
import type { LogRecordExporter } from './LogRecordExporter';
|
||||
import type { LogRecordProcessor } from '../LogRecordProcessor';
|
||||
|
||||
export abstract class BatchLogRecordProcessorBase<T extends BufferConfig>
|
||||
implements LogRecordProcessor
|
||||
{
|
||||
private readonly _maxExportBatchSize: number;
|
||||
private readonly _maxQueueSize: number;
|
||||
private readonly _scheduledDelayMillis: number;
|
||||
private readonly _exportTimeoutMillis: number;
|
||||
|
||||
private _finishedLogRecords: LogRecord[] = [];
|
||||
private _timer: NodeJS.Timeout | undefined;
|
||||
private _shutdownOnce: BindOnceFuture<void>;
|
||||
|
||||
constructor(private readonly _exporter: LogRecordExporter, config?: T) {
|
||||
const env = getEnv();
|
||||
this._maxExportBatchSize =
|
||||
config?.maxExportBatchSize ?? env.OTEL_BLRP_MAX_EXPORT_BATCH_SIZE;
|
||||
this._maxQueueSize = config?.maxQueueSize ?? env.OTEL_BLRP_MAX_QUEUE_SIZE;
|
||||
this._scheduledDelayMillis =
|
||||
config?.scheduledDelayMillis ?? env.OTEL_BLRP_SCHEDULE_DELAY;
|
||||
this._exportTimeoutMillis =
|
||||
config?.exportTimeoutMillis ?? env.OTEL_BLRP_EXPORT_TIMEOUT;
|
||||
|
||||
this._shutdownOnce = new BindOnceFuture(this._shutdown, this);
|
||||
|
||||
if (this._maxExportBatchSize > this._maxQueueSize) {
|
||||
diag.warn(
|
||||
'BatchLogRecordProcessor: maxExportBatchSize must be smaller or equal to maxQueueSize, setting maxExportBatchSize to match maxQueueSize'
|
||||
);
|
||||
this._maxExportBatchSize = this._maxQueueSize;
|
||||
}
|
||||
}
|
||||
|
||||
public onEmit(logRecord: LogRecord): void {
|
||||
if (this._shutdownOnce.isCalled) {
|
||||
return;
|
||||
}
|
||||
this._addToBuffer(logRecord);
|
||||
}
|
||||
|
||||
public forceFlush(): Promise<void> {
|
||||
if (this._shutdownOnce.isCalled) {
|
||||
return this._shutdownOnce.promise;
|
||||
}
|
||||
return this._flushAll();
|
||||
}
|
||||
|
||||
public shutdown(): Promise<void> {
|
||||
return this._shutdownOnce.call();
|
||||
}
|
||||
|
||||
private async _shutdown(): Promise<void> {
|
||||
this.onShutdown();
|
||||
await this._flushAll();
|
||||
await this._exporter.shutdown();
|
||||
}
|
||||
|
||||
/** Add a LogRecord in the buffer. */
|
||||
private _addToBuffer(logRecord: LogRecord) {
|
||||
if (this._finishedLogRecords.length >= this._maxQueueSize) {
|
||||
return;
|
||||
}
|
||||
this._finishedLogRecords.push(logRecord);
|
||||
this._maybeStartTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all LogRecords to the exporter respecting the batch size limit
|
||||
* This function is used only on forceFlush or shutdown,
|
||||
* for all other cases _flush should be used
|
||||
* */
|
||||
private _flushAll(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const promises = [];
|
||||
const batchCount = Math.ceil(
|
||||
this._finishedLogRecords.length / this._maxExportBatchSize
|
||||
);
|
||||
for (let i = 0; i < batchCount; i++) {
|
||||
promises.push(this._flushOneBatch());
|
||||
}
|
||||
Promise.all(promises)
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
private _flushOneBatch(): Promise<void> {
|
||||
this._clearTimer();
|
||||
if (this._finishedLogRecords.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
callWithTimeout(
|
||||
this._export(
|
||||
this._finishedLogRecords.splice(0, this._maxExportBatchSize)
|
||||
),
|
||||
this._exportTimeoutMillis
|
||||
)
|
||||
.then(() => resolve())
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
private _maybeStartTimer() {
|
||||
if (this._timer !== undefined) {
|
||||
return;
|
||||
}
|
||||
this._timer = setTimeout(() => {
|
||||
this._flushOneBatch()
|
||||
.then(() => {
|
||||
if (this._finishedLogRecords.length > 0) {
|
||||
this._clearTimer();
|
||||
this._maybeStartTimer();
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
globalErrorHandler(e);
|
||||
});
|
||||
}, this._scheduledDelayMillis);
|
||||
unrefTimer(this._timer);
|
||||
}
|
||||
|
||||
private _clearTimer() {
|
||||
if (this._timer !== undefined) {
|
||||
clearTimeout(this._timer);
|
||||
this._timer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _export(logRecords: LogRecord[]): Promise<ExportResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._exporter.export(logRecords, (res: ExportResult) => {
|
||||
if (res.code !== ExportResultCode.SUCCESS) {
|
||||
reject(
|
||||
res.error ??
|
||||
new Error(
|
||||
`BatchLogRecordProcessorBase: log record export failed (status ${res})`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract onShutdown(): void;
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 { ExportResult, hrTimeToMicroseconds } from '@opentelemetry/core';
|
||||
import { ExportResultCode } from '@opentelemetry/core';
|
||||
|
||||
import type { ReadableLogRecord } from './ReadableLogRecord';
|
||||
import type { LogRecordExporter } from './LogRecordExporter';
|
||||
|
||||
/**
|
||||
* This is implementation of {@link LogRecordExporter} that prints LogRecords to the
|
||||
* console. This class can be used for diagnostic purposes.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
export class ConsoleLogRecordExporter implements LogRecordExporter {
|
||||
/**
|
||||
* Export logs.
|
||||
* @param logs
|
||||
* @param resultCallback
|
||||
*/
|
||||
public export(
|
||||
logs: ReadableLogRecord[],
|
||||
resultCallback: (result: ExportResult) => void
|
||||
) {
|
||||
this._sendLogRecords(logs, resultCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the exporter.
|
||||
*/
|
||||
public shutdown(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* converts logRecord info into more readable format
|
||||
* @param logRecord
|
||||
*/
|
||||
private _exportInfo(logRecord: ReadableLogRecord) {
|
||||
return {
|
||||
timestamp: hrTimeToMicroseconds(logRecord.hrTime),
|
||||
traceId: logRecord.spanContext?.traceId,
|
||||
spanId: logRecord.spanContext?.spanId,
|
||||
traceFlags: logRecord.spanContext?.traceFlags,
|
||||
severityText: logRecord.severityText,
|
||||
severityNumber: logRecord.severityNumber,
|
||||
body: logRecord.body,
|
||||
attributes: logRecord.attributes,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Showing logs in console
|
||||
* @param logRecords
|
||||
* @param done
|
||||
*/
|
||||
private _sendLogRecords(
|
||||
logRecords: ReadableLogRecord[],
|
||||
done?: (result: ExportResult) => void
|
||||
): void {
|
||||
for (const logRecord of logRecords) {
|
||||
console.dir(this._exportInfo(logRecord), { depth: 3 });
|
||||
}
|
||||
done?.({ code: ExportResultCode.SUCCESS });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 type { ExportResult } from '@opentelemetry/core';
|
||||
import { ExportResultCode } from '@opentelemetry/core';
|
||||
|
||||
import type { ReadableLogRecord } from './ReadableLogRecord';
|
||||
import type { LogRecordExporter } from './LogRecordExporter';
|
||||
|
||||
/**
|
||||
* This class can be used for testing purposes. It stores the exported LogRecords
|
||||
* in a list in memory that can be retrieved using the `getFinishedLogRecords()`
|
||||
* method.
|
||||
*/
|
||||
export class InMemoryLogRecordExporter implements LogRecordExporter {
|
||||
private _finishedLogRecords: ReadableLogRecord[] = [];
|
||||
|
||||
/**
|
||||
* Indicates if the exporter has been "shutdown."
|
||||
* When false, exported log records will not be stored in-memory.
|
||||
*/
|
||||
protected _stopped = false;
|
||||
|
||||
public export(
|
||||
logs: ReadableLogRecord[],
|
||||
resultCallback: (result: ExportResult) => void
|
||||
) {
|
||||
if (this._stopped) {
|
||||
return resultCallback({
|
||||
code: ExportResultCode.FAILED,
|
||||
error: new Error('Exporter has been stopped'),
|
||||
});
|
||||
}
|
||||
|
||||
this._finishedLogRecords.push(...logs);
|
||||
resultCallback({ code: ExportResultCode.SUCCESS });
|
||||
}
|
||||
|
||||
public shutdown(): Promise<void> {
|
||||
this._stopped = true;
|
||||
this.reset();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public getFinishedLogRecords(): ReadableLogRecord[] {
|
||||
return this._finishedLogRecords;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._finishedLogRecords = [];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 type { ExportResult } from '@opentelemetry/core';
|
||||
|
||||
import type { ReadableLogRecord } from './ReadableLogRecord';
|
||||
|
||||
export interface LogRecordExporter {
|
||||
/**
|
||||
* Called to export {@link ReadableLogRecord}s.
|
||||
* @param logs the list of sampled LogRecords to be exported.
|
||||
*/
|
||||
export(
|
||||
logs: ReadableLogRecord[],
|
||||
resultCallback: (result: ExportResult) => void
|
||||
): void;
|
||||
|
||||
/** Stops the exporter. */
|
||||
shutdown(): Promise<void>;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 { LogRecordProcessor } from '../LogRecordProcessor';
|
||||
import { ReadableLogRecord } from './ReadableLogRecord';
|
||||
|
||||
export class NoopLogRecordProcessor implements LogRecordProcessor {
|
||||
forceFlush(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
onEmit(_logRecord: ReadableLogRecord): void {}
|
||||
|
||||
shutdown(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 type { IResource } from '@opentelemetry/resources';
|
||||
import type { Attributes, HrTime, SpanContext } from '@opentelemetry/api';
|
||||
import type { InstrumentationScope } from '@opentelemetry/core';
|
||||
import type { SeverityNumber } from '@opentelemetry/api-logs';
|
||||
|
||||
export interface ReadableLogRecord {
|
||||
readonly hrTime: HrTime;
|
||||
readonly spanContext?: SpanContext;
|
||||
readonly severityText?: string;
|
||||
readonly severityNumber?: SeverityNumber;
|
||||
readonly body?: string;
|
||||
readonly resource: IResource;
|
||||
readonly instrumentationScope: InstrumentationScope;
|
||||
readonly attributes: Attributes;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 type { ExportResult } from '@opentelemetry/core';
|
||||
import {
|
||||
BindOnceFuture,
|
||||
ExportResultCode,
|
||||
globalErrorHandler,
|
||||
} from '@opentelemetry/core';
|
||||
|
||||
import type { LogRecordExporter } from './LogRecordExporter';
|
||||
import type { LogRecordProcessor } from '../LogRecordProcessor';
|
||||
import type { LogRecord } from './../LogRecord';
|
||||
|
||||
export class SimpleLogRecordProcessor implements LogRecordProcessor {
|
||||
private _shutdownOnce: BindOnceFuture<void>;
|
||||
|
||||
constructor(private readonly _exporter: LogRecordExporter) {
|
||||
this._shutdownOnce = new BindOnceFuture(this._shutdown, this);
|
||||
}
|
||||
|
||||
public onEmit(logRecord: LogRecord): void {
|
||||
if (this._shutdownOnce.isCalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._exporter.export([logRecord], (res: ExportResult) => {
|
||||
if (res.code !== ExportResultCode.SUCCESS) {
|
||||
globalErrorHandler(
|
||||
res.error ??
|
||||
new Error(
|
||||
`SimpleLogRecordProcessor: log record export failed (status ${res})`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public forceFlush(): Promise<void> {
|
||||
// do nothing as all log records are being exported without waiting
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public shutdown(): Promise<void> {
|
||||
return this._shutdownOnce.call();
|
||||
}
|
||||
|
||||
private _shutdown(): Promise<void> {
|
||||
return this._exporter.shutdown();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
export {
|
||||
LoggerConfig,
|
||||
LogRecordLimits,
|
||||
BufferConfig,
|
||||
BatchLogRecordProcessorBrowserConfig,
|
||||
} from './types';
|
||||
export { LoggerProvider } from './LoggerProvider';
|
||||
export { Logger } from './Logger';
|
||||
export { LogRecord } from './LogRecord';
|
||||
export { LogRecordProcessor } from './LogRecordProcessor';
|
||||
export { ReadableLogRecord } from './export/ReadableLogRecord';
|
||||
export { NoopLogRecordProcessor } from './export/NoopLogRecordProcessor';
|
||||
export { ConsoleLogRecordExporter } from './export/ConsoleLogRecordExporter';
|
||||
export { LogRecordExporter } from './export/LogRecordExporter';
|
||||
export { SimpleLogRecordProcessor } from './export/SimpleLogRecordProcessor';
|
||||
export { InMemoryLogRecordExporter } from './export/InMemoryLogRecordExporter';
|
||||
export { BatchLogRecordProcessor } from './platform';
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 type { LogRecordExporter } from './../../../export/LogRecordExporter';
|
||||
import type { BatchLogRecordProcessorBrowserConfig } from '../../../types';
|
||||
import { BatchLogRecordProcessorBase } from '../../../export/BatchLogRecordProcessorBase';
|
||||
|
||||
export class BatchLogRecordProcessor extends BatchLogRecordProcessorBase<BatchLogRecordProcessorBrowserConfig> {
|
||||
private _visibilityChangeListener?: () => void;
|
||||
private _pageHideListener?: () => void;
|
||||
|
||||
constructor(
|
||||
exporter: LogRecordExporter,
|
||||
config?: BatchLogRecordProcessorBrowserConfig
|
||||
) {
|
||||
super(exporter, config);
|
||||
this._onInit(config);
|
||||
}
|
||||
|
||||
protected onShutdown(): void {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
if (this._visibilityChangeListener) {
|
||||
document.removeEventListener(
|
||||
'visibilitychange',
|
||||
this._visibilityChangeListener
|
||||
);
|
||||
}
|
||||
if (this._pageHideListener) {
|
||||
document.removeEventListener('pagehide', this._pageHideListener);
|
||||
}
|
||||
}
|
||||
|
||||
private _onInit(config?: BatchLogRecordProcessorBrowserConfig): void {
|
||||
if (
|
||||
config?.disableAutoFlushOnDocumentHide === true ||
|
||||
typeof document === 'undefined'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._visibilityChangeListener = () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
void this.forceFlush();
|
||||
}
|
||||
};
|
||||
this._pageHideListener = () => {
|
||||
void this.forceFlush();
|
||||
};
|
||||
document.addEventListener(
|
||||
'visibilitychange',
|
||||
this._visibilityChangeListener
|
||||
);
|
||||
|
||||
// use 'pagehide' event as a fallback for Safari; see https://bugs.webkit.org/show_bug.cgi?id=116769
|
||||
document.addEventListener('pagehide', this._pageHideListener);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
export { BatchLogRecordProcessor } from './export/BatchLogRecordProcessor';
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
export { BatchLogRecordProcessor } from './node';
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 type { BufferConfig } from '../../../types';
|
||||
import { BatchLogRecordProcessorBase } from '../../../export/BatchLogRecordProcessorBase';
|
||||
|
||||
export class BatchLogRecordProcessor extends BatchLogRecordProcessorBase<BufferConfig> {
|
||||
protected onShutdown(): void {}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
export { BatchLogRecordProcessor } from './export/BatchLogRecordProcessor';
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 type { IResource } from '@opentelemetry/resources';
|
||||
|
||||
export interface LoggerProviderConfig {
|
||||
/** Resource associated with trace telemetry */
|
||||
resource?: IResource;
|
||||
|
||||
/**
|
||||
* How long the forceFlush can run before it is cancelled.
|
||||
* The default value is 30000ms
|
||||
*/
|
||||
forceFlushTimeoutMillis?: number;
|
||||
|
||||
/** Log Record Limits*/
|
||||
logRecordLimits?: LogRecordLimits;
|
||||
}
|
||||
|
||||
export interface LoggerConfig {
|
||||
/** Log Record Limits*/
|
||||
logRecordLimits?: LogRecordLimits;
|
||||
|
||||
/** include Trace Context */
|
||||
includeTraceContext?: boolean;
|
||||
}
|
||||
|
||||
export interface LogRecordLimits {
|
||||
/** attributeValueLengthLimit is maximum allowed attribute value size */
|
||||
attributeValueLengthLimit?: number;
|
||||
|
||||
/** attributeCountLimit is number of attributes per LogRecord */
|
||||
attributeCountLimit?: number;
|
||||
}
|
||||
|
||||
/** Interface configuration for a buffer. */
|
||||
export interface BufferConfig {
|
||||
/** The maximum batch size of every export. It must be smaller or equal to
|
||||
* maxQueueSize. The default value is 512. */
|
||||
maxExportBatchSize?: number;
|
||||
|
||||
/** The delay interval in milliseconds between two consecutive exports.
|
||||
* The default value is 5000ms. */
|
||||
scheduledDelayMillis?: number;
|
||||
|
||||
/** How long the export can run before it is cancelled.
|
||||
* The default value is 30000ms */
|
||||
exportTimeoutMillis?: number;
|
||||
|
||||
/** The maximum queue size. After the size is reached log records are dropped.
|
||||
* The default value is 2048. */
|
||||
maxQueueSize?: number;
|
||||
}
|
||||
|
||||
export interface BatchLogRecordProcessorBrowserConfig extends BufferConfig {
|
||||
/** Disable flush when a user navigates to a new page, closes the tab or the browser, or,
|
||||
* on mobile, switches to a different app. Auto flush is enabled by default. */
|
||||
disableAutoFlushOnDocumentHide?: boolean;
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 * as assert from 'assert';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { LogRecordExporter } from '../../../src';
|
||||
import { BatchLogRecordProcessor } from '../../../src/platform/browser/export/BatchLogRecordProcessor';
|
||||
import { InMemoryLogRecordExporter } from './../../../src/export/InMemoryLogRecordExporter';
|
||||
|
||||
const describeDocument =
|
||||
typeof document === 'object' ? describe : describe.skip;
|
||||
|
||||
describeDocument('BatchLogRecordProcessor - web main context', () => {
|
||||
let visibilityState: VisibilityState = 'visible';
|
||||
let exporter: LogRecordExporter;
|
||||
let processor: BatchLogRecordProcessor;
|
||||
let forceFlushSpy: sinon.SinonStub;
|
||||
let visibilityChangeEvent: Event;
|
||||
let pageHideEvent: Event;
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.replaceGetter(document, 'visibilityState', () => visibilityState);
|
||||
visibilityState = 'visible';
|
||||
exporter = new InMemoryLogRecordExporter();
|
||||
processor = new BatchLogRecordProcessor(exporter, {});
|
||||
forceFlushSpy = sinon.stub(processor, 'forceFlush');
|
||||
visibilityChangeEvent = new Event('visibilitychange');
|
||||
pageHideEvent = new Event('pagehide');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('when document becomes hidden', () => {
|
||||
const testDocumentHide = (hideDocument: () => void) => {
|
||||
it('should force flush log records', () => {
|
||||
assert.strictEqual(forceFlushSpy.callCount, 0);
|
||||
hideDocument();
|
||||
assert.strictEqual(forceFlushSpy.callCount, 1);
|
||||
});
|
||||
|
||||
describe('AND shutdown has been called', () => {
|
||||
it('should NOT force flush log records', async () => {
|
||||
assert.strictEqual(forceFlushSpy.callCount, 0);
|
||||
await processor.shutdown();
|
||||
hideDocument();
|
||||
assert.strictEqual(forceFlushSpy.callCount, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AND disableAutoFlushOnDocumentHide configuration option', () => {
|
||||
it('set to false should force flush log records', () => {
|
||||
processor = new BatchLogRecordProcessor(exporter, {
|
||||
disableAutoFlushOnDocumentHide: false,
|
||||
});
|
||||
forceFlushSpy = sinon.stub(processor, 'forceFlush');
|
||||
assert.strictEqual(forceFlushSpy.callCount, 0);
|
||||
hideDocument();
|
||||
assert.strictEqual(forceFlushSpy.callCount, 1);
|
||||
});
|
||||
|
||||
it('set to true should NOT force flush log records', () => {
|
||||
processor = new BatchLogRecordProcessor(exporter, {
|
||||
disableAutoFlushOnDocumentHide: true,
|
||||
});
|
||||
forceFlushSpy = sinon.stub(processor, 'forceFlush');
|
||||
assert.strictEqual(forceFlushSpy.callCount, 0);
|
||||
hideDocument();
|
||||
assert.strictEqual(forceFlushSpy.callCount, 0);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('by the visibilitychange event', () => {
|
||||
testDocumentHide(() => {
|
||||
visibilityState = 'hidden';
|
||||
document.dispatchEvent(visibilityChangeEvent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('by the pagehide event', () => {
|
||||
testDocumentHide(() => {
|
||||
document.dispatchEvent(pageHideEvent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when document becomes visible', () => {
|
||||
it('should NOT force flush log records', () => {
|
||||
assert.strictEqual(forceFlushSpy.callCount, 0);
|
||||
document.dispatchEvent(visibilityChangeEvent);
|
||||
assert.strictEqual(forceFlushSpy.callCount, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('BatchLogRecordProcessor', () => {
|
||||
it('without exception', async () => {
|
||||
const exporter = new InMemoryLogRecordExporter();
|
||||
const logRecordProcessor = new BatchLogRecordProcessor(exporter);
|
||||
assert.ok(logRecordProcessor instanceof BatchLogRecordProcessor);
|
||||
|
||||
await logRecordProcessor.forceFlush();
|
||||
await logRecordProcessor.shutdown();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 * as sinon from 'sinon';
|
||||
import * as assert from 'assert';
|
||||
import {
|
||||
Attributes,
|
||||
AttributeValue,
|
||||
diag,
|
||||
ROOT_CONTEXT,
|
||||
trace,
|
||||
TraceFlags,
|
||||
} from '@opentelemetry/api';
|
||||
import * as logsAPI from '@opentelemetry/api-logs';
|
||||
import type { HrTime } from '@opentelemetry/api';
|
||||
import { hrTimeToMilliseconds, timeInputToHrTime } from '@opentelemetry/core';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
|
||||
import {
|
||||
LogRecordLimits,
|
||||
LogRecordProcessor,
|
||||
LogRecord,
|
||||
Logger,
|
||||
LoggerProvider,
|
||||
} from './../../src';
|
||||
import { invalidAttributes, validAttributes } from './utils';
|
||||
|
||||
const performanceTimeOrigin: HrTime = [1, 1];
|
||||
|
||||
const setup = (limits?: LogRecordLimits, data?: logsAPI.LogRecord) => {
|
||||
const instrumentationScope = {
|
||||
name: 'test name',
|
||||
version: 'test version',
|
||||
schemaUrl: 'test schema url',
|
||||
};
|
||||
const resource = Resource.default();
|
||||
const loggerProvider = new LoggerProvider({ resource });
|
||||
const logger = new Logger(
|
||||
instrumentationScope,
|
||||
{
|
||||
logRecordLimits: limits,
|
||||
},
|
||||
loggerProvider
|
||||
);
|
||||
const logRecord = new LogRecord(logger, data || {});
|
||||
return { logger, logRecord, instrumentationScope, resource };
|
||||
};
|
||||
|
||||
describe('LogRecord', () => {
|
||||
describe('constructor', () => {
|
||||
it('should create an instance', () => {
|
||||
const { logRecord } = setup();
|
||||
assert.ok(logRecord instanceof LogRecord);
|
||||
});
|
||||
|
||||
it('should have a default timestamp', () => {
|
||||
const { logRecord } = setup();
|
||||
assert.ok(logRecord.hrTime !== undefined);
|
||||
assert.ok(
|
||||
hrTimeToMilliseconds(logRecord.hrTime) >
|
||||
hrTimeToMilliseconds(performanceTimeOrigin)
|
||||
);
|
||||
});
|
||||
|
||||
it('should return LogRecord', () => {
|
||||
const spanContext = {
|
||||
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
|
||||
spanId: '6e0c63257de34c92',
|
||||
traceFlags: TraceFlags.SAMPLED,
|
||||
};
|
||||
const activeContext = trace.setSpanContext(ROOT_CONTEXT, spanContext);
|
||||
|
||||
const logRecordData: logsAPI.LogRecord = {
|
||||
timestamp: new Date().getTime(),
|
||||
severityNumber: logsAPI.SeverityNumber.DEBUG,
|
||||
severityText: 'DEBUG',
|
||||
body: 'this is a body',
|
||||
attributes: {
|
||||
name: 'test name',
|
||||
},
|
||||
context: activeContext,
|
||||
};
|
||||
const { logRecord, resource, instrumentationScope } = setup(
|
||||
undefined,
|
||||
logRecordData
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
logRecord.hrTime,
|
||||
timeInputToHrTime(logRecordData.timestamp!)
|
||||
);
|
||||
assert.strictEqual(
|
||||
logRecord.severityNumber,
|
||||
logRecordData.severityNumber
|
||||
);
|
||||
assert.strictEqual(logRecord.severityText, logRecordData.severityText);
|
||||
assert.strictEqual(logRecord.body, logRecordData.body);
|
||||
assert.deepStrictEqual(logRecord.attributes, logRecordData.attributes);
|
||||
assert.strictEqual(logRecord.spanContext?.traceId, spanContext.traceId);
|
||||
assert.strictEqual(logRecord.spanContext?.spanId, spanContext.spanId);
|
||||
assert.strictEqual(
|
||||
logRecord.spanContext?.traceFlags,
|
||||
spanContext.traceFlags
|
||||
);
|
||||
assert.deepStrictEqual(logRecord.resource, resource);
|
||||
assert.deepStrictEqual(
|
||||
logRecord.instrumentationScope,
|
||||
instrumentationScope
|
||||
);
|
||||
});
|
||||
|
||||
it('should return LogRecord with attributes', () => {
|
||||
const logRecordData: logsAPI.LogRecord = {
|
||||
timestamp: new Date().getTime(),
|
||||
severityNumber: logsAPI.SeverityNumber.DEBUG,
|
||||
severityText: 'DEBUG',
|
||||
body: 'this is a body',
|
||||
attributes: {
|
||||
name: 'test name',
|
||||
},
|
||||
};
|
||||
const { logRecord } = setup(undefined, logRecordData);
|
||||
|
||||
assert.deepStrictEqual(logRecord.attributes, { name: 'test name' });
|
||||
|
||||
logRecord.setAttribute('attr1', 'value1');
|
||||
assert.deepStrictEqual(logRecord.attributes, {
|
||||
name: 'test name',
|
||||
attr1: 'value1',
|
||||
});
|
||||
|
||||
logRecord.setAttributes({ attr2: 123, attr1: false });
|
||||
assert.deepStrictEqual(logRecord.attributes, {
|
||||
name: 'test name',
|
||||
attr1: false,
|
||||
attr2: 123,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAttribute', () => {
|
||||
describe('when default options set', () => {
|
||||
it('should set an attribute', () => {
|
||||
const { logRecord } = setup();
|
||||
for (const [k, v] of Object.entries(validAttributes)) {
|
||||
logRecord.setAttribute(k, v);
|
||||
}
|
||||
for (const [k, v] of Object.entries(invalidAttributes)) {
|
||||
logRecord.setAttribute(k, v as unknown as AttributeValue);
|
||||
}
|
||||
assert.deepStrictEqual(logRecord.attributes, validAttributes);
|
||||
});
|
||||
|
||||
it('should be able to overwrite attributes', () => {
|
||||
const { logRecord } = setup();
|
||||
logRecord.setAttribute('overwrite', 'initial value');
|
||||
logRecord.setAttribute('overwrite', 'overwritten value');
|
||||
assert.deepStrictEqual(logRecord.attributes, {
|
||||
overwrite: 'overwritten value',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when logRecordLimits options set', () => {
|
||||
describe('when "attributeCountLimit" option defined', () => {
|
||||
const { logRecord } = setup({ attributeCountLimit: 100 });
|
||||
for (let i = 0; i < 150; i++) {
|
||||
logRecord.setAttribute(`foo${i}`, `bar${i}`);
|
||||
}
|
||||
|
||||
it('should remove / drop all remaining values after the number of values exceeds this limit', () => {
|
||||
const { attributes } = logRecord;
|
||||
assert.strictEqual(Object.keys(attributes).length, 100);
|
||||
assert.strictEqual(attributes.foo0, 'bar0');
|
||||
assert.strictEqual(attributes.foo99, 'bar99');
|
||||
assert.strictEqual(attributes.foo149, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "attributeValueLengthLimit" option defined', () => {
|
||||
const { logRecord } = setup({ attributeValueLengthLimit: 5 });
|
||||
const { attributes } = logRecord;
|
||||
|
||||
it('should truncate value which length exceeds this limit', () => {
|
||||
logRecord.setAttribute('attr-with-more-length', 'abcdefgh');
|
||||
assert.strictEqual(attributes['attr-with-more-length'], 'abcde');
|
||||
});
|
||||
|
||||
it('should truncate value of arrays which exceeds this limit', () => {
|
||||
logRecord.setAttribute('attr-array-of-strings', [
|
||||
'abcdefgh',
|
||||
'abc',
|
||||
'abcde',
|
||||
'',
|
||||
]);
|
||||
logRecord.setAttribute('attr-array-of-bool', [true, false]);
|
||||
assert.deepStrictEqual(attributes['attr-array-of-strings'], [
|
||||
'abcde',
|
||||
'abc',
|
||||
'abcde',
|
||||
'',
|
||||
]);
|
||||
assert.deepStrictEqual(attributes['attr-array-of-bool'], [
|
||||
true,
|
||||
false,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not truncate value which length not exceeds this limit', () => {
|
||||
logRecord.setAttribute('attr-with-less-length', 'abc');
|
||||
assert.strictEqual(attributes['attr-with-less-length'], 'abc');
|
||||
});
|
||||
|
||||
it('should return same value for non-string values', () => {
|
||||
logRecord.setAttribute('attr-non-string', true);
|
||||
assert.strictEqual(attributes['attr-non-string'], true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "attributeValueLengthLimit" option is invalid', () => {
|
||||
const { logRecord } = setup({ attributeValueLengthLimit: -5 });
|
||||
const { attributes } = logRecord;
|
||||
|
||||
it('should not truncate any value', () => {
|
||||
logRecord.setAttribute('attr-not-truncate', 'abcdefgh');
|
||||
logRecord.setAttribute('attr-array-of-strings', [
|
||||
'abcdefgh',
|
||||
'abc',
|
||||
'abcde',
|
||||
]);
|
||||
assert.deepStrictEqual(attributes['attr-not-truncate'], 'abcdefgh');
|
||||
assert.deepStrictEqual(attributes['attr-array-of-strings'], [
|
||||
'abcdefgh',
|
||||
'abc',
|
||||
'abcde',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAttributes', () => {
|
||||
it('should be able to set multiple attributes', () => {
|
||||
const { logRecord } = setup();
|
||||
logRecord.setAttributes(validAttributes);
|
||||
logRecord.setAttributes(invalidAttributes as unknown as Attributes);
|
||||
assert.deepStrictEqual(logRecord.attributes, validAttributes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should rewrite body/severityNumber/severityText', () => {
|
||||
const currentTime = new Date().getTime();
|
||||
const logRecordData: logsAPI.LogRecord = {
|
||||
timestamp: currentTime,
|
||||
severityNumber: logsAPI.SeverityNumber.DEBUG,
|
||||
severityText: 'DEBUG',
|
||||
body: 'this is a body',
|
||||
attributes: {
|
||||
name: 'test name',
|
||||
},
|
||||
};
|
||||
|
||||
const newBody = 'this is a new body';
|
||||
const newSeverityNumber = logsAPI.SeverityNumber.INFO;
|
||||
const newSeverityText = 'INFO';
|
||||
|
||||
it('should rewrite directly through the property method', () => {
|
||||
const { logRecord } = setup(undefined, logRecordData);
|
||||
|
||||
logRecord.body = newBody;
|
||||
logRecord.severityNumber = newSeverityNumber;
|
||||
logRecord.severityText = newSeverityText;
|
||||
|
||||
assert.deepStrictEqual(logRecord.body, newBody);
|
||||
assert.deepStrictEqual(logRecord.severityNumber, newSeverityNumber);
|
||||
assert.deepStrictEqual(logRecord.severityText, newSeverityText);
|
||||
});
|
||||
|
||||
it('should rewrite using the set method', () => {
|
||||
const { logRecord } = setup(undefined, logRecordData);
|
||||
|
||||
logRecord.setBody(newBody);
|
||||
logRecord.setSeverityNumber(newSeverityNumber);
|
||||
logRecord.setSeverityText(newSeverityText);
|
||||
|
||||
assert.deepStrictEqual(logRecord.body, newBody);
|
||||
assert.deepStrictEqual(logRecord.severityNumber, newSeverityNumber);
|
||||
assert.deepStrictEqual(logRecord.severityText, newSeverityText);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should be read-only(body/severityNumber/severityText) if makeReadonly has been called', () => {
|
||||
const currentTime = new Date().getTime();
|
||||
const logRecordData: logsAPI.LogRecord = {
|
||||
timestamp: currentTime,
|
||||
severityNumber: logsAPI.SeverityNumber.DEBUG,
|
||||
severityText: 'DEBUG',
|
||||
body: 'this is a body',
|
||||
attributes: {
|
||||
name: 'test name',
|
||||
},
|
||||
};
|
||||
|
||||
const newBody = 'this is a new body';
|
||||
const newSeverityNumber = logsAPI.SeverityNumber.INFO;
|
||||
const newSeverityText = 'INFO';
|
||||
|
||||
it('should not rewrite directly through the property method', () => {
|
||||
const warnStub = sinon.spy(diag, 'warn');
|
||||
const { logRecord } = setup(undefined, logRecordData);
|
||||
logRecord.makeReadonly();
|
||||
|
||||
logRecord.body = newBody;
|
||||
logRecord.severityNumber = newSeverityNumber;
|
||||
logRecord.severityText = newSeverityText;
|
||||
|
||||
assert.deepStrictEqual(logRecord.body, logRecordData.body);
|
||||
assert.deepStrictEqual(
|
||||
logRecord.severityNumber,
|
||||
logRecordData.severityNumber
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
logRecord.severityText,
|
||||
logRecordData.severityText
|
||||
);
|
||||
sinon.assert.callCount(warnStub, 3);
|
||||
sinon.assert.alwaysCalledWith(
|
||||
warnStub,
|
||||
'Can not execute the operation on emitted log record'
|
||||
);
|
||||
warnStub.restore();
|
||||
});
|
||||
|
||||
it('should not rewrite using the set method', () => {
|
||||
const warnStub = sinon.spy(diag, 'warn');
|
||||
const { logRecord } = setup(undefined, logRecordData);
|
||||
logRecord.makeReadonly();
|
||||
|
||||
logRecord.setBody(newBody);
|
||||
logRecord.setSeverityNumber(newSeverityNumber);
|
||||
logRecord.setSeverityText(newSeverityText);
|
||||
|
||||
assert.deepStrictEqual(logRecord.body, logRecordData.body);
|
||||
assert.deepStrictEqual(
|
||||
logRecord.severityNumber,
|
||||
logRecordData.severityNumber
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
logRecord.severityText,
|
||||
logRecordData.severityText
|
||||
);
|
||||
sinon.assert.callCount(warnStub, 3);
|
||||
sinon.assert.alwaysCalledWith(
|
||||
warnStub,
|
||||
'Can not execute the operation on emitted log record'
|
||||
);
|
||||
warnStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('log record processor', () => {
|
||||
it('should call onEmit synchronously when log record is emitted', () => {
|
||||
let emitted = false;
|
||||
const processor: LogRecordProcessor = {
|
||||
onEmit: () => {
|
||||
emitted = true;
|
||||
},
|
||||
forceFlush: () => Promise.resolve(),
|
||||
shutdown: () => Promise.resolve(),
|
||||
};
|
||||
const provider = new LoggerProvider();
|
||||
provider.addLogRecordProcessor(processor);
|
||||
provider.getLogger('default').emit({ body: 'test' });
|
||||
assert.ok(emitted);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 * as assert from 'assert';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { LogRecord, Logger, LoggerConfig, LoggerProvider } from '../../src';
|
||||
import { loadDefaultConfig } from '../../src/config';
|
||||
import { context } from '@opentelemetry/api';
|
||||
|
||||
const setup = (loggerConfig: LoggerConfig = {}) => {
|
||||
const logger = new Logger(
|
||||
{
|
||||
name: 'test name',
|
||||
version: 'test version',
|
||||
schemaUrl: 'test schema url',
|
||||
},
|
||||
loggerConfig,
|
||||
new LoggerProvider()
|
||||
);
|
||||
return { logger };
|
||||
};
|
||||
|
||||
describe('Logger', () => {
|
||||
describe('constructor', () => {
|
||||
it('should create an instance', () => {
|
||||
const { logger } = setup();
|
||||
assert.ok(logger instanceof Logger);
|
||||
});
|
||||
|
||||
it('should a default value with config.includeTraceContext', () => {
|
||||
const { logger } = setup();
|
||||
assert.ok(
|
||||
logger['_loggerConfig'].includeTraceContext ===
|
||||
loadDefaultConfig().includeTraceContext
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('emit', () => {
|
||||
it('should emit a logRecord instance', () => {
|
||||
const { logger } = setup();
|
||||
const callSpy = sinon.spy(logger.getActiveLogRecordProcessor(), 'onEmit');
|
||||
logger.emit({
|
||||
body: 'test log body',
|
||||
});
|
||||
assert.ok(callSpy.called);
|
||||
});
|
||||
|
||||
it('should make log record instance readonly after emit it', () => {
|
||||
const { logger } = setup();
|
||||
const makeOnlySpy = sinon.spy(LogRecord.prototype, 'makeReadonly');
|
||||
logger.emit({
|
||||
body: 'test log body',
|
||||
});
|
||||
assert.ok(makeOnlySpy.called);
|
||||
});
|
||||
|
||||
it('should emit with current Context when includeTraceContext is true', () => {
|
||||
const { logger } = setup({ includeTraceContext: true });
|
||||
const callSpy = sinon.spy(logger.getActiveLogRecordProcessor(), 'onEmit');
|
||||
logger.emit({
|
||||
body: 'test log body',
|
||||
});
|
||||
assert.ok(callSpy.calledWith(sinon.match.any, context.active()));
|
||||
});
|
||||
|
||||
it('should emit with empty Context when includeTraceContext is false', () => {
|
||||
const { logger } = setup({ includeTraceContext: false });
|
||||
const callSpy = sinon.spy(logger.getActiveLogRecordProcessor(), 'onEmit');
|
||||
logger.emit({
|
||||
body: 'test log body',
|
||||
});
|
||||
assert.ok(callSpy.calledWith(sinon.match.any, undefined));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 { logs, NoopLogger } from '@opentelemetry/api-logs';
|
||||
import { diag } from '@opentelemetry/api';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
import * as assert from 'assert';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { Logger, LoggerProvider, NoopLogRecordProcessor } from '../../src';
|
||||
import { loadDefaultConfig } from '../../src/config';
|
||||
import { DEFAULT_LOGGER_NAME } from './../../src/LoggerProvider';
|
||||
|
||||
describe('LoggerProvider', () => {
|
||||
let envSource: Record<string, any>;
|
||||
|
||||
if (typeof process === 'undefined') {
|
||||
envSource = globalThis as unknown as Record<string, any>;
|
||||
} else {
|
||||
envSource = process.env as Record<string, any>;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// to avoid actually registering the LoggerProvider and leaking env to other tests
|
||||
sinon.stub(logs, 'setGlobalLoggerProvider');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
describe('when options not defined', () => {
|
||||
it('should construct an instance', () => {
|
||||
const provider = new LoggerProvider();
|
||||
assert.ok(provider instanceof LoggerProvider);
|
||||
});
|
||||
|
||||
it('should use noop log record processor by default and no diag error', () => {
|
||||
const errorStub = sinon.spy(diag, 'error');
|
||||
const provider = new LoggerProvider();
|
||||
const processors = provider.getActiveLogRecordProcessor().processors;
|
||||
assert.ok(processors.length === 1);
|
||||
assert.ok(processors[0] instanceof NoopLogRecordProcessor);
|
||||
sinon.assert.notCalled(errorStub);
|
||||
});
|
||||
|
||||
it('should have default resource if not pass', () => {
|
||||
const provider = new LoggerProvider();
|
||||
const { resource } = provider;
|
||||
assert.deepStrictEqual(resource, Resource.default());
|
||||
});
|
||||
|
||||
it('should have default forceFlushTimeoutMillis if not pass', () => {
|
||||
const provider = new LoggerProvider();
|
||||
const activeProcessor = provider.getActiveLogRecordProcessor();
|
||||
assert.ok(
|
||||
activeProcessor.forceFlushTimeoutMillis ===
|
||||
loadDefaultConfig().forceFlushTimeoutMillis
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user sets unavailable exporter', () => {
|
||||
it('should use noop log record processor by default', () => {
|
||||
const provider = new LoggerProvider();
|
||||
const processors = provider.getActiveLogRecordProcessor().processors;
|
||||
assert.ok(processors.length === 1);
|
||||
assert.ok(processors[0] instanceof NoopLogRecordProcessor);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logRecordLimits', () => {
|
||||
describe('when not defined default values', () => {
|
||||
it('should have logger with default values', () => {
|
||||
const logger = new LoggerProvider({}).getLogger('default') as Logger;
|
||||
assert.deepStrictEqual(logger.getLogRecordLimits(), {
|
||||
attributeValueLengthLimit: Infinity,
|
||||
attributeCountLimit: 128,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "attributeCountLimit" is defined', () => {
|
||||
it('should have logger with defined value', () => {
|
||||
const logger = new LoggerProvider({
|
||||
logRecordLimits: {
|
||||
attributeCountLimit: 100,
|
||||
},
|
||||
}).getLogger('default') as Logger;
|
||||
const logRecordLimits = logger.getLogRecordLimits();
|
||||
assert.strictEqual(logRecordLimits.attributeCountLimit, 100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when "attributeValueLengthLimit" is defined', () => {
|
||||
it('should have logger with defined value', () => {
|
||||
const logger = new LoggerProvider({
|
||||
logRecordLimits: {
|
||||
attributeValueLengthLimit: 10,
|
||||
},
|
||||
}).getLogger('default') as Logger;
|
||||
const logRecordLimits = logger.getLogRecordLimits();
|
||||
assert.strictEqual(logRecordLimits.attributeValueLengthLimit, 10);
|
||||
});
|
||||
|
||||
it('should have logger with negative "attributeValueLengthLimit" value', () => {
|
||||
const logger = new LoggerProvider({
|
||||
logRecordLimits: {
|
||||
attributeValueLengthLimit: -10,
|
||||
},
|
||||
}).getLogger('default') as Logger;
|
||||
const logRecordLimits = logger.getLogRecordLimits();
|
||||
assert.strictEqual(logRecordLimits.attributeValueLengthLimit, -10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when attribute value length limit is defined via env', () => {
|
||||
it('should have attribute value length limit as default of Infinity', () => {
|
||||
envSource.OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT = 'Infinity';
|
||||
const logger = new LoggerProvider().getLogger('default') as Logger;
|
||||
const logRecordLimits = logger.getLogRecordLimits();
|
||||
assert.strictEqual(
|
||||
logRecordLimits.attributeValueLengthLimit,
|
||||
Infinity
|
||||
);
|
||||
delete envSource.OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when attribute value length limit is not defined via env', () => {
|
||||
it('should use default value of Infinity', () => {
|
||||
const logger = new LoggerProvider().getLogger('default') as Logger;
|
||||
const logRecordLimits = logger.getLogRecordLimits();
|
||||
assert.strictEqual(
|
||||
logRecordLimits.attributeValueLengthLimit,
|
||||
Infinity
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when attribute count limit is defined via env', () => {
|
||||
it('should have attribute count limits as defined in env', () => {
|
||||
envSource.OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT = '35';
|
||||
const logger = new LoggerProvider().getLogger('default') as Logger;
|
||||
const logRecordLimits = logger.getLogRecordLimits();
|
||||
assert.strictEqual(logRecordLimits.attributeCountLimit, 35);
|
||||
delete envSource.OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT;
|
||||
});
|
||||
it('should have attribute count limit as default of 128', () => {
|
||||
envSource.OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT = '128';
|
||||
const logger = new LoggerProvider().getLogger('default') as Logger;
|
||||
const logRecordLimits = logger.getLogRecordLimits();
|
||||
assert.strictEqual(logRecordLimits.attributeCountLimit, 128);
|
||||
delete envSource.OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT;
|
||||
});
|
||||
});
|
||||
|
||||
describe('when attribute count limit is not defined via env', () => {
|
||||
it('should use default value of 128', () => {
|
||||
const logger = new LoggerProvider().getLogger('default') as Logger;
|
||||
const logRecordLimits = logger.getLogRecordLimits();
|
||||
assert.strictEqual(logRecordLimits.attributeCountLimit, 128);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLogger', () => {
|
||||
const testName = 'test name';
|
||||
const testVersion = 'test version';
|
||||
const testSchemaURL = 'test schema url';
|
||||
|
||||
it('should create a logger instance with default name if the name is invalid ', () => {
|
||||
const provider = new LoggerProvider();
|
||||
const logger = provider.getLogger('') as Logger;
|
||||
assert.ok(logger.instrumentationScope.name === DEFAULT_LOGGER_NAME);
|
||||
});
|
||||
|
||||
it("should create a logger instance if the name doesn't exist", () => {
|
||||
const provider = new LoggerProvider();
|
||||
assert.ok(provider.getActiveLoggers().size === 0);
|
||||
provider.getLogger(testName);
|
||||
assert.ok(provider.getActiveLoggers().size === 1);
|
||||
});
|
||||
|
||||
it('should create A new object if the name & version & schemaUrl are not unique', () => {
|
||||
const provider = new LoggerProvider();
|
||||
assert.ok(provider.getActiveLoggers().size === 0);
|
||||
|
||||
provider.getLogger(testName);
|
||||
assert.ok(provider.getActiveLoggers().size === 1);
|
||||
provider.getLogger(testName, testVersion);
|
||||
assert.ok(provider.getActiveLoggers().size === 2);
|
||||
provider.getLogger(testName, testVersion, { schemaUrl: testSchemaURL });
|
||||
assert.ok(provider.getActiveLoggers().size === 3);
|
||||
});
|
||||
|
||||
it('should not create A new object if the name & version & schemaUrl are unique', () => {
|
||||
const provider = new LoggerProvider();
|
||||
|
||||
assert.ok(provider.getActiveLoggers().size === 0);
|
||||
provider.getLogger(testName);
|
||||
assert.ok(provider.getActiveLoggers().size === 1);
|
||||
const logger1 = provider.getLogger(testName, testVersion, {
|
||||
schemaUrl: testSchemaURL,
|
||||
});
|
||||
assert.ok(provider.getActiveLoggers().size === 2);
|
||||
const logger2 = provider.getLogger(testName, testVersion, {
|
||||
schemaUrl: testSchemaURL,
|
||||
});
|
||||
assert.ok(provider.getActiveLoggers().size === 2);
|
||||
assert.ok(logger2 instanceof Logger);
|
||||
assert.ok(logger1 === logger2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addLogRecordProcessor', () => {
|
||||
it('should add logRecord processor', () => {
|
||||
const logRecordProcessor = new NoopLogRecordProcessor();
|
||||
const provider = new LoggerProvider();
|
||||
provider.addLogRecordProcessor(logRecordProcessor);
|
||||
assert.strictEqual(
|
||||
provider.getActiveLogRecordProcessor().processors.length,
|
||||
1
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.forceFlush()', () => {
|
||||
it('should call forceFlush on all registered log record processors', done => {
|
||||
sinon.restore();
|
||||
const forceFlushStub = sinon.stub(
|
||||
NoopLogRecordProcessor.prototype,
|
||||
'forceFlush'
|
||||
);
|
||||
forceFlushStub.resolves();
|
||||
|
||||
const provider = new LoggerProvider();
|
||||
const logRecordProcessorOne = new NoopLogRecordProcessor();
|
||||
const logRecordProcessorTwo = new NoopLogRecordProcessor();
|
||||
|
||||
provider.addLogRecordProcessor(logRecordProcessorOne);
|
||||
provider.addLogRecordProcessor(logRecordProcessorTwo);
|
||||
|
||||
provider
|
||||
.forceFlush()
|
||||
.then(() => {
|
||||
sinon.restore();
|
||||
assert(forceFlushStub.calledTwice);
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
sinon.restore();
|
||||
done(error);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when calling forceFlush on all registered processors fails', done => {
|
||||
sinon.restore();
|
||||
|
||||
const forceFlushStub = sinon.stub(
|
||||
NoopLogRecordProcessor.prototype,
|
||||
'forceFlush'
|
||||
);
|
||||
forceFlushStub.returns(Promise.reject('Error'));
|
||||
|
||||
const provider = new LoggerProvider();
|
||||
const logRecordProcessorOne = new NoopLogRecordProcessor();
|
||||
const logRecordProcessorTwo = new NoopLogRecordProcessor();
|
||||
|
||||
provider.addLogRecordProcessor(logRecordProcessorOne);
|
||||
provider.addLogRecordProcessor(logRecordProcessorTwo);
|
||||
|
||||
provider
|
||||
.forceFlush()
|
||||
.then(() => {
|
||||
sinon.restore();
|
||||
done(new Error('Successful forceFlush not expected'));
|
||||
})
|
||||
.catch(_error => {
|
||||
sinon.restore();
|
||||
sinon.assert.calledTwice(forceFlushStub);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('.shutdown()', () => {
|
||||
it('should trigger shutdown when manually invoked', () => {
|
||||
const provider = new LoggerProvider();
|
||||
const shutdownStub = sinon.stub(
|
||||
provider.getActiveLogRecordProcessor(),
|
||||
'shutdown'
|
||||
);
|
||||
provider.shutdown();
|
||||
sinon.assert.calledOnce(shutdownStub);
|
||||
});
|
||||
|
||||
it('get a noop logger on shutdown', () => {
|
||||
const provider = new LoggerProvider();
|
||||
provider.shutdown();
|
||||
const logger = provider.getLogger('default', '1.0.0');
|
||||
// returned tracer should be no-op, not instance of Logger (from SDK)
|
||||
assert.ok(logger instanceof NoopLogger);
|
||||
});
|
||||
|
||||
it('should not force flush on shutdown', () => {
|
||||
const provider = new LoggerProvider();
|
||||
const logRecordProcessor = new NoopLogRecordProcessor();
|
||||
provider.addLogRecordProcessor(logRecordProcessor);
|
||||
const forceFlushStub = sinon.stub(
|
||||
provider.getActiveLogRecordProcessor(),
|
||||
'forceFlush'
|
||||
);
|
||||
const warnStub = sinon.spy(diag, 'warn');
|
||||
provider.shutdown();
|
||||
provider.forceFlush();
|
||||
sinon.assert.notCalled(forceFlushStub);
|
||||
sinon.assert.calledOnce(warnStub);
|
||||
});
|
||||
|
||||
it('should not shutdown on shutdown', () => {
|
||||
const provider = new LoggerProvider();
|
||||
const logRecordProcessor = new NoopLogRecordProcessor();
|
||||
provider.addLogRecordProcessor(logRecordProcessor);
|
||||
const shutdownStub = sinon.stub(
|
||||
provider.getActiveLogRecordProcessor(),
|
||||
'shutdown'
|
||||
);
|
||||
const warnStub = sinon.spy(diag, 'warn');
|
||||
provider.shutdown();
|
||||
provider.shutdown();
|
||||
sinon.assert.calledOnce(shutdownStub);
|
||||
sinon.assert.calledOnce(warnStub);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 * as assert from 'assert';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import type { LogRecordProcessor, ReadableLogRecord } from '../../src';
|
||||
import {
|
||||
LoggerProvider,
|
||||
InMemoryLogRecordExporter,
|
||||
SimpleLogRecordProcessor,
|
||||
} from './../../src';
|
||||
import { loadDefaultConfig } from '../../src/config';
|
||||
import { MultiLogRecordProcessor } from './../../src/MultiLogRecordProcessor';
|
||||
import { assertRejects } from '../test-utils';
|
||||
|
||||
class TestProcessor implements LogRecordProcessor {
|
||||
logRecords: ReadableLogRecord[] = [];
|
||||
onEmit(logRecord: ReadableLogRecord): void {
|
||||
this.logRecords.push(logRecord);
|
||||
}
|
||||
shutdown(): Promise<void> {
|
||||
this.logRecords = [];
|
||||
return Promise.resolve();
|
||||
}
|
||||
forceFlush(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
const setup = (processors: LogRecordProcessor[] = []) => {
|
||||
const { forceFlushTimeoutMillis } = loadDefaultConfig();
|
||||
const multiProcessor = new MultiLogRecordProcessor(
|
||||
processors,
|
||||
forceFlushTimeoutMillis
|
||||
);
|
||||
return { multiProcessor, forceFlushTimeoutMillis };
|
||||
};
|
||||
|
||||
describe('MultiLogRecordProcessor', () => {
|
||||
describe('constructor', () => {
|
||||
it('should create an instance', () => {
|
||||
assert.ok(setup().multiProcessor instanceof MultiLogRecordProcessor);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onEmit', () => {
|
||||
it('should handle empty log record processor', () => {
|
||||
const { multiProcessor } = setup();
|
||||
const provider = new LoggerProvider();
|
||||
provider.addLogRecordProcessor(multiProcessor);
|
||||
const logger = provider.getLogger('default');
|
||||
logger.emit({ body: 'one' });
|
||||
multiProcessor.shutdown();
|
||||
});
|
||||
|
||||
it('should handle one log record processor', () => {
|
||||
const processor1 = new TestProcessor();
|
||||
const { multiProcessor } = setup([processor1]);
|
||||
const provider = new LoggerProvider();
|
||||
provider.addLogRecordProcessor(multiProcessor);
|
||||
const logger = provider.getLogger('default');
|
||||
assert.strictEqual(processor1.logRecords.length, 0);
|
||||
|
||||
logger.emit({ body: 'one' });
|
||||
assert.strictEqual(processor1.logRecords.length, 1);
|
||||
multiProcessor.shutdown();
|
||||
});
|
||||
|
||||
it('should handle two log record processor', async () => {
|
||||
const processor1 = new TestProcessor();
|
||||
const processor2 = new TestProcessor();
|
||||
const { multiProcessor } = setup([processor1, processor2]);
|
||||
const provider = new LoggerProvider();
|
||||
provider.addLogRecordProcessor(multiProcessor);
|
||||
const logger = provider.getLogger('default');
|
||||
|
||||
assert.strictEqual(processor1.logRecords.length, 0);
|
||||
assert.strictEqual(
|
||||
processor1.logRecords.length,
|
||||
processor2.logRecords.length
|
||||
);
|
||||
|
||||
logger.emit({ body: 'one' });
|
||||
assert.strictEqual(processor1.logRecords.length, 1);
|
||||
assert.strictEqual(
|
||||
processor1.logRecords.length,
|
||||
processor2.logRecords.length
|
||||
);
|
||||
|
||||
await multiProcessor.shutdown();
|
||||
assert.strictEqual(processor1.logRecords.length, 0);
|
||||
assert.strictEqual(
|
||||
processor1.logRecords.length,
|
||||
processor2.logRecords.length
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('forceFlush', () => {
|
||||
it('should force log record processors to flush', () => {
|
||||
let flushed = false;
|
||||
const processor: LogRecordProcessor = {
|
||||
forceFlush: () => {
|
||||
flushed = true;
|
||||
return Promise.resolve();
|
||||
},
|
||||
onEmit: () => {},
|
||||
shutdown: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
const { multiProcessor } = setup([processor]);
|
||||
multiProcessor.forceFlush();
|
||||
assert.ok(flushed);
|
||||
});
|
||||
|
||||
it('should wait for all log record processors to finish flushing', done => {
|
||||
let flushed = 0;
|
||||
const processor1 = new SimpleLogRecordProcessor(
|
||||
new InMemoryLogRecordExporter()
|
||||
);
|
||||
const processor2 = new SimpleLogRecordProcessor(
|
||||
new InMemoryLogRecordExporter()
|
||||
);
|
||||
|
||||
const spy1 = sinon.stub(processor1, 'forceFlush').callsFake(() => {
|
||||
flushed++;
|
||||
return Promise.resolve();
|
||||
});
|
||||
const spy2 = sinon.stub(processor2, 'forceFlush').callsFake(() => {
|
||||
flushed++;
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
const { multiProcessor } = setup([processor1, processor2]);
|
||||
multiProcessor.forceFlush().then(() => {
|
||||
sinon.assert.calledOnce(spy1);
|
||||
sinon.assert.calledOnce(spy2);
|
||||
assert.strictEqual(flushed, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error if either time out', async () => {
|
||||
const processor: LogRecordProcessor = {
|
||||
forceFlush: () =>
|
||||
new Promise<void>(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, forceFlushTimeoutMillis + 1000);
|
||||
}),
|
||||
onEmit: () => {},
|
||||
shutdown: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
|
||||
const clock = sinon.useFakeTimers();
|
||||
const { multiProcessor, forceFlushTimeoutMillis } = setup([
|
||||
processor,
|
||||
new TestProcessor(),
|
||||
]);
|
||||
const res = multiProcessor.forceFlush();
|
||||
clock.tick(forceFlushTimeoutMillis + 1000);
|
||||
clock.restore();
|
||||
await assertRejects(res, /Operation timed out/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shutdown', () => {
|
||||
it('should export log records on manual shutdown from two log record processor', async () => {
|
||||
const processor1 = new TestProcessor();
|
||||
const processor2 = new TestProcessor();
|
||||
const { multiProcessor } = setup([processor1, processor2]);
|
||||
const provider = new LoggerProvider();
|
||||
provider.addLogRecordProcessor(multiProcessor);
|
||||
const logger = provider.getLogger('default');
|
||||
|
||||
assert.strictEqual(processor1.logRecords.length, 0);
|
||||
assert.strictEqual(
|
||||
processor1.logRecords.length,
|
||||
processor2.logRecords.length
|
||||
);
|
||||
|
||||
logger.emit({ body: 'one' });
|
||||
assert.strictEqual(processor1.logRecords.length, 1);
|
||||
assert.strictEqual(
|
||||
processor1.logRecords.length,
|
||||
processor2.logRecords.length
|
||||
);
|
||||
|
||||
await provider.shutdown();
|
||||
assert.strictEqual(processor1.logRecords.length, 0);
|
||||
assert.strictEqual(
|
||||
processor1.logRecords.length,
|
||||
processor2.logRecords.length
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,333 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 * as assert from 'assert';
|
||||
import * as sinon from 'sinon';
|
||||
import {
|
||||
ExportResultCode,
|
||||
getEnv,
|
||||
loggingErrorHandler,
|
||||
setGlobalErrorHandler,
|
||||
} from '@opentelemetry/core';
|
||||
|
||||
import {
|
||||
BufferConfig,
|
||||
LogRecordLimits,
|
||||
LogRecord,
|
||||
InMemoryLogRecordExporter,
|
||||
LoggerProvider,
|
||||
Logger,
|
||||
} from '../../../src';
|
||||
import { BatchLogRecordProcessorBase } from '../../../src/export/BatchLogRecordProcessorBase';
|
||||
|
||||
class BatchLogRecordProcessor extends BatchLogRecordProcessorBase<BufferConfig> {
|
||||
onInit() {}
|
||||
onShutdown() {}
|
||||
}
|
||||
|
||||
const createLogRecord = (limits?: LogRecordLimits): LogRecord => {
|
||||
const logger = new Logger(
|
||||
{
|
||||
name: 'test name',
|
||||
version: 'test version',
|
||||
schemaUrl: 'test schema url',
|
||||
},
|
||||
{
|
||||
logRecordLimits: limits,
|
||||
},
|
||||
new LoggerProvider()
|
||||
);
|
||||
const logRecord = new LogRecord(logger, { body: 'body' });
|
||||
return logRecord;
|
||||
};
|
||||
|
||||
describe('BatchLogRecordProcessorBase', () => {
|
||||
const defaultBufferConfig = {
|
||||
maxExportBatchSize: 5,
|
||||
scheduledDelayMillis: 2500,
|
||||
};
|
||||
let exporter: InMemoryLogRecordExporter;
|
||||
|
||||
beforeEach(() => {
|
||||
exporter = new InMemoryLogRecordExporter();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
exporter.reset();
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should create a BatchLogRecordProcessor instance', () => {
|
||||
const processor = new BatchLogRecordProcessor(exporter);
|
||||
assert.ok(processor instanceof BatchLogRecordProcessor);
|
||||
processor.shutdown();
|
||||
});
|
||||
|
||||
it('should create a BatchLogRecordProcessor instance with config', () => {
|
||||
const bufferConfig = {
|
||||
maxExportBatchSize: 5,
|
||||
scheduledDelayMillis: 2500,
|
||||
exportTimeoutMillis: 2000,
|
||||
maxQueueSize: 200,
|
||||
};
|
||||
const processor = new BatchLogRecordProcessor(exporter, bufferConfig);
|
||||
assert.ok(processor instanceof BatchLogRecordProcessor);
|
||||
assert.strictEqual(
|
||||
processor['_maxExportBatchSize'],
|
||||
bufferConfig.maxExportBatchSize
|
||||
);
|
||||
assert.strictEqual(processor['_maxQueueSize'], bufferConfig.maxQueueSize);
|
||||
assert.strictEqual(
|
||||
processor['_scheduledDelayMillis'],
|
||||
bufferConfig.scheduledDelayMillis
|
||||
);
|
||||
assert.strictEqual(
|
||||
processor['_exportTimeoutMillis'],
|
||||
bufferConfig.exportTimeoutMillis
|
||||
);
|
||||
processor.shutdown();
|
||||
});
|
||||
|
||||
it('should create a BatchLogRecordProcessor instance with empty config', () => {
|
||||
const processor = new BatchLogRecordProcessor(exporter);
|
||||
|
||||
const {
|
||||
OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
|
||||
OTEL_BSP_MAX_QUEUE_SIZE,
|
||||
OTEL_BSP_SCHEDULE_DELAY,
|
||||
OTEL_BSP_EXPORT_TIMEOUT,
|
||||
} = getEnv();
|
||||
assert.ok(processor instanceof BatchLogRecordProcessor);
|
||||
assert.strictEqual(
|
||||
processor['_maxExportBatchSize'],
|
||||
OTEL_BSP_MAX_EXPORT_BATCH_SIZE
|
||||
);
|
||||
assert.strictEqual(processor['_maxQueueSize'], OTEL_BSP_MAX_QUEUE_SIZE);
|
||||
assert.strictEqual(
|
||||
processor['_scheduledDelayMillis'],
|
||||
OTEL_BSP_SCHEDULE_DELAY
|
||||
);
|
||||
assert.strictEqual(
|
||||
processor['_exportTimeoutMillis'],
|
||||
OTEL_BSP_EXPORT_TIMEOUT
|
||||
);
|
||||
processor.shutdown();
|
||||
});
|
||||
|
||||
it('maxExportBatchSize must be smaller or equal to maxQueueSize', () => {
|
||||
const bufferConfig = {
|
||||
maxExportBatchSize: 200,
|
||||
maxQueueSize: 100,
|
||||
};
|
||||
const processor = new BatchLogRecordProcessor(exporter, bufferConfig);
|
||||
assert.strictEqual(
|
||||
processor['_maxExportBatchSize'],
|
||||
processor['_maxQueueSize']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onEmit', () => {
|
||||
it('should export the log records with buffer size reached', done => {
|
||||
const clock = sinon.useFakeTimers();
|
||||
const processor = new BatchLogRecordProcessor(
|
||||
exporter,
|
||||
defaultBufferConfig
|
||||
);
|
||||
for (let i = 0; i < defaultBufferConfig.maxExportBatchSize; i++) {
|
||||
const logRecord = createLogRecord();
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
processor.onEmit(logRecord);
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
}
|
||||
const logRecord = createLogRecord();
|
||||
processor.onEmit(logRecord);
|
||||
setTimeout(async () => {
|
||||
assert.strictEqual(
|
||||
exporter.getFinishedLogRecords().length,
|
||||
defaultBufferConfig.maxExportBatchSize
|
||||
);
|
||||
await processor.shutdown();
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
done();
|
||||
}, defaultBufferConfig.scheduledDelayMillis + 1000);
|
||||
clock.tick(defaultBufferConfig.scheduledDelayMillis + 1000);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('should force flush when timeout exceeded', done => {
|
||||
const clock = sinon.useFakeTimers();
|
||||
const processor = new BatchLogRecordProcessor(
|
||||
exporter,
|
||||
defaultBufferConfig
|
||||
);
|
||||
for (let i = 0; i < defaultBufferConfig.maxExportBatchSize; i++) {
|
||||
const logRecord = createLogRecord();
|
||||
processor.onEmit(logRecord);
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
}
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(
|
||||
exporter.getFinishedLogRecords().length,
|
||||
defaultBufferConfig.maxExportBatchSize
|
||||
);
|
||||
done();
|
||||
}, defaultBufferConfig.scheduledDelayMillis + 1000);
|
||||
clock.tick(defaultBufferConfig.scheduledDelayMillis + 1000);
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('should not export empty log record lists', done => {
|
||||
const spy = sinon.spy(exporter, 'export');
|
||||
const clock = sinon.useFakeTimers();
|
||||
new BatchLogRecordProcessor(exporter, defaultBufferConfig);
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
sinon.assert.notCalled(spy);
|
||||
done();
|
||||
}, defaultBufferConfig.scheduledDelayMillis + 1000);
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
clock.tick(defaultBufferConfig.scheduledDelayMillis + 1000);
|
||||
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('should export each log record exactly once with buffer size reached multiple times', done => {
|
||||
const originalTimeout = setTimeout;
|
||||
const clock = sinon.useFakeTimers();
|
||||
const processor = new BatchLogRecordProcessor(
|
||||
exporter,
|
||||
defaultBufferConfig
|
||||
);
|
||||
const totalLogRecords = defaultBufferConfig.maxExportBatchSize * 2;
|
||||
for (let i = 0; i < totalLogRecords; i++) {
|
||||
const logRecord = createLogRecord();
|
||||
processor.onEmit(logRecord);
|
||||
}
|
||||
const logRecord = createLogRecord();
|
||||
processor.onEmit(logRecord);
|
||||
clock.tick(defaultBufferConfig.scheduledDelayMillis + 10);
|
||||
originalTimeout(() => {
|
||||
clock.tick(defaultBufferConfig.scheduledDelayMillis + 10);
|
||||
originalTimeout(async () => {
|
||||
clock.tick(defaultBufferConfig.scheduledDelayMillis + 10);
|
||||
clock.restore();
|
||||
assert.strictEqual(
|
||||
exporter.getFinishedLogRecords().length,
|
||||
totalLogRecords + 1
|
||||
);
|
||||
await processor.shutdown();
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should call globalErrorHandler when exporting fails', done => {
|
||||
const clock = sinon.useFakeTimers();
|
||||
const expectedError = new Error('Exporter failed');
|
||||
sinon.stub(exporter, 'export').callsFake((_, callback) => {
|
||||
setTimeout(() => {
|
||||
callback({ code: ExportResultCode.FAILED, error: expectedError });
|
||||
}, 0);
|
||||
});
|
||||
const errorHandlerSpy = sinon.spy();
|
||||
setGlobalErrorHandler(errorHandlerSpy);
|
||||
const processor = new BatchLogRecordProcessor(
|
||||
exporter,
|
||||
defaultBufferConfig
|
||||
);
|
||||
for (let i = 0; i < defaultBufferConfig.maxExportBatchSize; i++) {
|
||||
const logRecord = createLogRecord();
|
||||
processor.onEmit(logRecord);
|
||||
}
|
||||
clock.tick(defaultBufferConfig.scheduledDelayMillis + 1000);
|
||||
clock.restore();
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(errorHandlerSpy.callCount, 1);
|
||||
const [[error]] = errorHandlerSpy.args;
|
||||
assert.deepStrictEqual(error, expectedError);
|
||||
// reset global error handler
|
||||
setGlobalErrorHandler(loggingErrorHandler());
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should drop logRecords when there are more logRecords then "maxQueueSize"', () => {
|
||||
const maxQueueSize = 6;
|
||||
const processor = new BatchLogRecordProcessor(exporter, { maxQueueSize });
|
||||
const logRecord = createLogRecord();
|
||||
for (let i = 0; i < maxQueueSize + 10; i++) {
|
||||
processor.onEmit(logRecord);
|
||||
}
|
||||
assert.strictEqual(processor['_finishedLogRecords'].length, 6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('forceFlush', () => {
|
||||
it('should force flush on demand', () => {
|
||||
const processor = new BatchLogRecordProcessor(
|
||||
exporter,
|
||||
defaultBufferConfig
|
||||
);
|
||||
for (let i = 0; i < defaultBufferConfig.maxExportBatchSize; i++) {
|
||||
const logRecord = createLogRecord();
|
||||
processor.onEmit(logRecord);
|
||||
}
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
processor.forceFlush();
|
||||
assert.strictEqual(
|
||||
exporter.getFinishedLogRecords().length,
|
||||
defaultBufferConfig.maxExportBatchSize
|
||||
);
|
||||
});
|
||||
|
||||
it('should call an async callback when flushing is complete', async () => {
|
||||
const processor = new BatchLogRecordProcessor(exporter);
|
||||
const logRecord = createLogRecord();
|
||||
processor.onEmit(logRecord);
|
||||
await processor.forceFlush();
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shutdown', () => {
|
||||
it('should call onShutdown', async () => {
|
||||
const processor = new BatchLogRecordProcessor(exporter);
|
||||
const onShutdownSpy = sinon.stub(processor, 'onShutdown');
|
||||
assert.strictEqual(onShutdownSpy.callCount, 0);
|
||||
await processor.shutdown();
|
||||
assert.strictEqual(onShutdownSpy.callCount, 1);
|
||||
});
|
||||
|
||||
it('should call an async callback when shutdown is complete', async () => {
|
||||
let exportedLogRecords = 0;
|
||||
sinon.stub(exporter, 'export').callsFake((logRecords, callback) => {
|
||||
setTimeout(() => {
|
||||
exportedLogRecords = exportedLogRecords + logRecords.length;
|
||||
callback({ code: ExportResultCode.SUCCESS });
|
||||
}, 0);
|
||||
});
|
||||
const processor = new BatchLogRecordProcessor(exporter);
|
||||
const logRecord = createLogRecord();
|
||||
processor.onEmit(logRecord);
|
||||
await processor.shutdown();
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
assert.strictEqual(exportedLogRecords, 1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 * as assert from 'assert';
|
||||
import * as sinon from 'sinon';
|
||||
import { SeverityNumber } from '@opentelemetry/api-logs';
|
||||
|
||||
import {
|
||||
LoggerProvider,
|
||||
ConsoleLogRecordExporter,
|
||||
SimpleLogRecordProcessor,
|
||||
} from './../../../src';
|
||||
|
||||
/* eslint-disable no-console */
|
||||
describe('ConsoleLogRecordExporter', () => {
|
||||
let previousConsoleDir: typeof console.dir;
|
||||
|
||||
beforeEach(() => {
|
||||
previousConsoleDir = console.dir;
|
||||
console.dir = () => {};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.dir = previousConsoleDir;
|
||||
});
|
||||
|
||||
describe('export', () => {
|
||||
it('should export information about log record', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
const consoleExporter = new ConsoleLogRecordExporter();
|
||||
const spyConsole = sinon.spy(console, 'dir');
|
||||
const spyExport = sinon.spy(consoleExporter, 'export');
|
||||
const provider = new LoggerProvider();
|
||||
provider.addLogRecordProcessor(
|
||||
new SimpleLogRecordProcessor(consoleExporter)
|
||||
);
|
||||
|
||||
provider.getLogger('default').emit({
|
||||
body: 'body1',
|
||||
severityNumber: SeverityNumber.DEBUG,
|
||||
severityText: 'DEBUG',
|
||||
});
|
||||
|
||||
const logRecords = spyExport.args[0];
|
||||
const firstLogRecord = logRecords[0][0];
|
||||
const consoleArgs = spyConsole.args[0];
|
||||
const consoleLogRecord = consoleArgs[0];
|
||||
const keys = Object.keys(consoleLogRecord).sort().join(',');
|
||||
|
||||
const expectedKeys = [
|
||||
'attributes',
|
||||
'body',
|
||||
'severityNumber',
|
||||
'severityText',
|
||||
'spanId',
|
||||
'timestamp',
|
||||
'traceFlags',
|
||||
'traceId',
|
||||
].join(',');
|
||||
|
||||
assert.ok(firstLogRecord.body === 'body1');
|
||||
assert.ok(firstLogRecord.severityNumber === SeverityNumber.DEBUG);
|
||||
assert.ok(firstLogRecord.severityText === 'DEBUG');
|
||||
assert.ok(keys === expectedKeys, 'expectedKeys');
|
||||
|
||||
assert.ok(spyExport.calledOnce);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 * as assert from 'assert';
|
||||
import { SeverityNumber } from '@opentelemetry/api-logs';
|
||||
import { ExportResult, ExportResultCode } from '@opentelemetry/core';
|
||||
|
||||
import {
|
||||
LoggerProvider,
|
||||
InMemoryLogRecordExporter,
|
||||
SimpleLogRecordProcessor,
|
||||
} from '../../../src';
|
||||
|
||||
const setup = () => {
|
||||
const provider = new LoggerProvider();
|
||||
const memoryExporter = new InMemoryLogRecordExporter();
|
||||
provider.addLogRecordProcessor(new SimpleLogRecordProcessor(memoryExporter));
|
||||
return { provider, memoryExporter };
|
||||
};
|
||||
|
||||
describe('InMemoryLogRecordExporter', () => {
|
||||
describe('export', () => {
|
||||
it('should export information about log record', () => {
|
||||
const { provider, memoryExporter } = setup();
|
||||
provider.getLogger('default').emit({
|
||||
body: 'body1',
|
||||
severityNumber: SeverityNumber.DEBUG,
|
||||
severityText: 'DEBUG',
|
||||
});
|
||||
const logRecords = memoryExporter.getFinishedLogRecords();
|
||||
assert.ok(logRecords.length === 1);
|
||||
|
||||
const firstLogRecord = logRecords[0];
|
||||
assert.ok(firstLogRecord.body === 'body1');
|
||||
assert.ok(firstLogRecord.severityNumber === SeverityNumber.DEBUG);
|
||||
assert.ok(firstLogRecord.severityText === 'DEBUG');
|
||||
});
|
||||
|
||||
it('should return the success result', () => {
|
||||
const { memoryExporter } = setup();
|
||||
memoryExporter.export([], (result: ExportResult) => {
|
||||
assert.strictEqual(result.code, ExportResultCode.SUCCESS);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('shutdown', () => {
|
||||
it('should clear all log records', async () => {
|
||||
const { provider, memoryExporter } = setup();
|
||||
provider.getLogger('default').emit({
|
||||
body: 'body1',
|
||||
severityNumber: SeverityNumber.DEBUG,
|
||||
severityText: 'DEBUG',
|
||||
});
|
||||
assert.ok(memoryExporter.getFinishedLogRecords().length === 1);
|
||||
await memoryExporter.shutdown();
|
||||
assert.strictEqual(memoryExporter.getFinishedLogRecords().length, 0);
|
||||
});
|
||||
|
||||
it('should return failed result after shutdown', () => {
|
||||
const { memoryExporter } = setup();
|
||||
memoryExporter.shutdown();
|
||||
|
||||
// after shutdown export should fail
|
||||
memoryExporter.export([], (result: ExportResult) => {
|
||||
assert.strictEqual(result.code, ExportResultCode.FAILED);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should clear all log records', () => {
|
||||
const { provider, memoryExporter } = setup();
|
||||
provider.getLogger('default').emit({
|
||||
body: 'body1',
|
||||
severityNumber: SeverityNumber.DEBUG,
|
||||
severityText: 'DEBUG',
|
||||
});
|
||||
assert.ok(memoryExporter.getFinishedLogRecords().length === 1);
|
||||
memoryExporter.reset();
|
||||
assert.strictEqual(memoryExporter.getFinishedLogRecords().length, 0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 * as assert from 'assert';
|
||||
import * as sinon from 'sinon';
|
||||
import {
|
||||
ExportResultCode,
|
||||
loggingErrorHandler,
|
||||
setGlobalErrorHandler,
|
||||
} from '@opentelemetry/core';
|
||||
|
||||
import {
|
||||
InMemoryLogRecordExporter,
|
||||
LogRecordExporter,
|
||||
SimpleLogRecordProcessor,
|
||||
LogRecord,
|
||||
LoggerProvider,
|
||||
Logger,
|
||||
} from './../../../src';
|
||||
|
||||
const setup = (exporter: LogRecordExporter) => {
|
||||
const processor = new SimpleLogRecordProcessor(exporter);
|
||||
return { exporter, processor };
|
||||
};
|
||||
|
||||
describe('SimpleLogRecordProcessor', () => {
|
||||
describe('constructor', () => {
|
||||
it('should create an instance', () => {
|
||||
assert.ok(
|
||||
setup(new InMemoryLogRecordExporter()).processor instanceof
|
||||
SimpleLogRecordProcessor
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onEmit', () => {
|
||||
it('should handle onEmit', async () => {
|
||||
const exporter = new InMemoryLogRecordExporter();
|
||||
const { processor } = setup(exporter);
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
|
||||
const logger = new Logger(
|
||||
{
|
||||
name: 'test name',
|
||||
version: 'test version',
|
||||
schemaUrl: 'test schema url',
|
||||
},
|
||||
{},
|
||||
new LoggerProvider()
|
||||
);
|
||||
const logRecord = new LogRecord(logger, {
|
||||
body: 'body',
|
||||
});
|
||||
processor.onEmit(logRecord);
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 1);
|
||||
|
||||
await processor.shutdown();
|
||||
assert.strictEqual(exporter.getFinishedLogRecords().length, 0);
|
||||
});
|
||||
|
||||
it('should call globalErrorHandler when exporting fails', async () => {
|
||||
const expectedError = new Error('Exporter failed');
|
||||
const exporter: LogRecordExporter = {
|
||||
export: (_, callback) =>
|
||||
setTimeout(
|
||||
() =>
|
||||
callback({ code: ExportResultCode.FAILED, error: expectedError }),
|
||||
0
|
||||
),
|
||||
shutdown: () => Promise.resolve(),
|
||||
};
|
||||
const { processor } = setup(exporter);
|
||||
|
||||
const logger = new Logger(
|
||||
{
|
||||
name: 'test name',
|
||||
version: 'test version',
|
||||
schemaUrl: 'test schema url',
|
||||
},
|
||||
{},
|
||||
new LoggerProvider()
|
||||
);
|
||||
const logRecord = new LogRecord(logger, {
|
||||
body: 'body',
|
||||
});
|
||||
const errorHandlerSpy = sinon.spy();
|
||||
setGlobalErrorHandler(errorHandlerSpy);
|
||||
processor.onEmit(logRecord);
|
||||
await new Promise<void>(resolve => setTimeout(() => resolve(), 0));
|
||||
assert.strictEqual(errorHandlerSpy.callCount, 1);
|
||||
const [[error]] = errorHandlerSpy.args;
|
||||
assert.deepStrictEqual(error, expectedError);
|
||||
// reset global error handler
|
||||
setGlobalErrorHandler(loggingErrorHandler());
|
||||
});
|
||||
});
|
||||
|
||||
describe('shutdown', () => {
|
||||
it('should handle shutdown', async () => {
|
||||
const shutdownSpy = sinon.spy();
|
||||
const exporter: LogRecordExporter = {
|
||||
export: (_, callback) => callback({ code: ExportResultCode.SUCCESS }),
|
||||
shutdown: shutdownSpy,
|
||||
};
|
||||
const { processor } = setup(exporter);
|
||||
await processor.shutdown();
|
||||
assert.ok(shutdownSpy.callCount === 1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
export const validAttributes = {
|
||||
string: 'string',
|
||||
number: 0,
|
||||
bool: true,
|
||||
'array<string>': ['str1', 'str2'],
|
||||
'array<number>': [1, 2],
|
||||
'array<bool>': [true, false],
|
||||
};
|
||||
|
||||
export const invalidAttributes = {
|
||||
// invalid attribute type object
|
||||
object: { foo: 'bar' },
|
||||
// invalid attribute inhomogeneous array
|
||||
'non-homogeneous-array': [0, ''],
|
||||
// This empty length attribute should not be set
|
||||
'': 'empty-key',
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
{
|
||||
const testsContext = require.context('./', true, /test$/);
|
||||
testsContext.keys().forEach(testsContext);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
{
|
||||
const testsContext = require.context('./', true, /test$/);
|
||||
testsContext.keys().forEach(testsContext);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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 * as assert from 'assert';
|
||||
|
||||
interface ErrorLikeConstructor {
|
||||
new (): Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node.js v8.x and browser compatible `assert.rejects`.
|
||||
*/
|
||||
export async function assertRejects(
|
||||
actual: any,
|
||||
expected: RegExp | ErrorLikeConstructor
|
||||
) {
|
||||
let rejected;
|
||||
try {
|
||||
if (typeof actual === 'function') {
|
||||
await actual();
|
||||
} else {
|
||||
await actual;
|
||||
}
|
||||
} catch (err) {
|
||||
rejected = true;
|
||||
assert.throws(() => {
|
||||
throw err;
|
||||
}, expected);
|
||||
}
|
||||
assert(rejected, 'Promise not rejected');
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.esm.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "build/esm",
|
||||
"rootDir": "src",
|
||||
"tsBuildInfoFile": "build/esm/tsconfig.esm.tsbuildinfo"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../api"
|
||||
},
|
||||
{
|
||||
"path": "../../../packages/opentelemetry-core"
|
||||
},
|
||||
{
|
||||
"path": "../../../packages/opentelemetry-resources"
|
||||
},
|
||||
{
|
||||
"path": "../api-logs"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.esnext.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "build/esnext",
|
||||
"rootDir": "src",
|
||||
"tsBuildInfoFile": "build/esnext/tsconfig.esnext.tsbuildinfo"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../api"
|
||||
},
|
||||
{
|
||||
"path": "../../../packages/opentelemetry-core"
|
||||
},
|
||||
{
|
||||
"path": "../../../packages/opentelemetry-resources"
|
||||
},
|
||||
{
|
||||
"path": "../api-logs"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "build",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../api"
|
||||
},
|
||||
{
|
||||
"path": "../../../packages/opentelemetry-core"
|
||||
},
|
||||
{
|
||||
"path": "../../../packages/opentelemetry-resources"
|
||||
},
|
||||
{
|
||||
"path": "../api-logs"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -38,6 +38,7 @@ export * from './trace/TraceState';
|
|||
export * from './utils/environment';
|
||||
export * from './utils/merge';
|
||||
export * from './utils/sampling';
|
||||
export * from './utils/timeout';
|
||||
export * from './utils/url';
|
||||
export * from './utils/wrap';
|
||||
export * from './utils/callback';
|
||||
|
|
|
@ -41,10 +41,16 @@ const ENVIRONMENT_NUMBERS_KEYS = [
|
|||
'OTEL_BSP_MAX_EXPORT_BATCH_SIZE',
|
||||
'OTEL_BSP_MAX_QUEUE_SIZE',
|
||||
'OTEL_BSP_SCHEDULE_DELAY',
|
||||
'OTEL_BLRP_EXPORT_TIMEOUT',
|
||||
'OTEL_BLRP_MAX_EXPORT_BATCH_SIZE',
|
||||
'OTEL_BLRP_MAX_QUEUE_SIZE',
|
||||
'OTEL_BLRP_SCHEDULE_DELAY',
|
||||
'OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT',
|
||||
'OTEL_ATTRIBUTE_COUNT_LIMIT',
|
||||
'OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT',
|
||||
'OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT',
|
||||
'OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT',
|
||||
'OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT',
|
||||
'OTEL_SPAN_EVENT_COUNT_LIMIT',
|
||||
'OTEL_SPAN_LINK_COUNT_LIMIT',
|
||||
'OTEL_SPAN_ATTRIBUTE_PER_EVENT_COUNT_LIMIT',
|
||||
|
@ -102,6 +108,7 @@ export type ENVIRONMENT = {
|
|||
OTEL_TRACES_EXPORTER?: string;
|
||||
OTEL_TRACES_SAMPLER_ARG?: string;
|
||||
OTEL_TRACES_SAMPLER?: string;
|
||||
OTEL_LOGS_EXPORTER?: string;
|
||||
OTEL_EXPORTER_OTLP_INSECURE?: string;
|
||||
OTEL_EXPORTER_OTLP_TRACES_INSECURE?: string;
|
||||
OTEL_EXPORTER_OTLP_METRICS_INSECURE?: string;
|
||||
|
@ -151,6 +158,10 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
|
|||
OTEL_BSP_MAX_EXPORT_BATCH_SIZE: 512,
|
||||
OTEL_BSP_MAX_QUEUE_SIZE: 2048,
|
||||
OTEL_BSP_SCHEDULE_DELAY: 5000,
|
||||
OTEL_BLRP_EXPORT_TIMEOUT: 30000,
|
||||
OTEL_BLRP_MAX_EXPORT_BATCH_SIZE: 512,
|
||||
OTEL_BLRP_MAX_QUEUE_SIZE: 2048,
|
||||
OTEL_BLRP_SCHEDULE_DELAY: 5000,
|
||||
OTEL_EXPORTER_JAEGER_AGENT_HOST: '',
|
||||
OTEL_EXPORTER_JAEGER_AGENT_PORT: 6832,
|
||||
OTEL_EXPORTER_JAEGER_ENDPOINT: '',
|
||||
|
@ -175,6 +186,9 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
|
|||
OTEL_ATTRIBUTE_COUNT_LIMIT: DEFAULT_ATTRIBUTE_COUNT_LIMIT,
|
||||
OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT,
|
||||
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: DEFAULT_ATTRIBUTE_COUNT_LIMIT,
|
||||
OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT:
|
||||
DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT,
|
||||
OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT: DEFAULT_ATTRIBUTE_COUNT_LIMIT,
|
||||
OTEL_SPAN_EVENT_COUNT_LIMIT: 128,
|
||||
OTEL_SPAN_LINK_COUNT_LIMIT: 128,
|
||||
OTEL_SPAN_ATTRIBUTE_PER_EVENT_COUNT_LIMIT:
|
||||
|
@ -184,6 +198,7 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
|
|||
OTEL_TRACES_EXPORTER: '',
|
||||
OTEL_TRACES_SAMPLER: TracesSamplerValues.ParentBasedAlwaysOn,
|
||||
OTEL_TRACES_SAMPLER_ARG: '',
|
||||
OTEL_LOGS_EXPORTER: '',
|
||||
OTEL_EXPORTER_OTLP_INSECURE: '',
|
||||
OTEL_EXPORTER_OTLP_TRACES_INSECURE: '',
|
||||
OTEL_EXPORTER_OTLP_METRICS_INSECURE: '',
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Error that is thrown on timeouts.
|
||||
*/
|
||||
export class TimeoutError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
|
||||
// manually adjust prototype to retain `instanceof` functionality when targeting ES5, see:
|
||||
// https://github.com/Microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
||||
Object.setPrototypeOf(this, TimeoutError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a timeout to a promise and rejects if the specified timeout has elapsed. Also rejects if the specified promise
|
||||
* rejects, and resolves if the specified promise resolves.
|
||||
*
|
||||
* <p> NOTE: this operation will continue even after it throws a {@link TimeoutError}.
|
||||
*
|
||||
* @param promise promise to use with timeout.
|
||||
* @param timeout the timeout in milliseconds until the returned promise is rejected.
|
||||
*/
|
||||
export function callWithTimeout<T>(
|
||||
promise: Promise<T>,
|
||||
timeout: number
|
||||
): Promise<T> {
|
||||
let timeoutHandle: ReturnType<typeof setTimeout>;
|
||||
|
||||
const timeoutPromise = new Promise<never>(function timeoutFunction(
|
||||
_resolve,
|
||||
reject
|
||||
) {
|
||||
timeoutHandle = setTimeout(function timeoutHandler() {
|
||||
reject(new TimeoutError('Operation timed out.'));
|
||||
}, timeout);
|
||||
});
|
||||
|
||||
return Promise.race([promise, timeoutPromise]).then(
|
||||
result => {
|
||||
clearTimeout(timeoutHandle);
|
||||
return result;
|
||||
},
|
||||
reason => {
|
||||
clearTimeout(timeoutHandle);
|
||||
throw reason;
|
||||
}
|
||||
);
|
||||
}
|
|
@ -24,14 +24,24 @@ const appRoot = process.cwd();
|
|||
const packageJsonUrl = path.resolve(`${appRoot}/package.json`);
|
||||
const pjson = require(packageJsonUrl);
|
||||
|
||||
if (pjson.dependencies && pjson.dependencies["@opentelemetry/api"])
|
||||
throw new Error(`Package ${pjson.name} depends on API but it should be a peer dependency`);
|
||||
const needCheckPackages = ['@opentelemetry/api', '@opentelemetry/api-logs'];
|
||||
|
||||
const peerVersion = pjson.peerDependencies && pjson.peerDependencies["@opentelemetry/api"]
|
||||
const devVersion = pjson.devDependencies && pjson.devDependencies["@opentelemetry/api"]
|
||||
if (peerVersion) {
|
||||
function checkPackage(package) {
|
||||
if (pjson.dependencies && pjson.dependencies[package])
|
||||
throw new Error(
|
||||
`Package ${pjson.name} depends on API but it should be a peer dependency`
|
||||
);
|
||||
|
||||
const peerVersion = pjson.peerDependencies && pjson.peerDependencies[package];
|
||||
const devVersion = pjson.devDependencies && pjson.devDependencies[package];
|
||||
if (peerVersion) {
|
||||
if (!semver.subset(devVersion, peerVersion)) {
|
||||
throw new Error(`Package ${pjson.name} depends on peer API version ${peerVersion} but version ${devVersion} in development`);
|
||||
throw new Error(
|
||||
`Package ${pjson.name} depends on peer API version ${peerVersion} but version ${devVersion} in development`
|
||||
);
|
||||
}
|
||||
console.log(`${pjson.name} OK`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
needCheckPackages.forEach(checkPackage);
|
||||
|
|
|
@ -41,6 +41,9 @@
|
|||
{
|
||||
"path": "experimental/packages/otlp-transformer/tsconfig.esm.json"
|
||||
},
|
||||
{
|
||||
"path": "experimental/packages/sdk-logs/tsconfig.esm.json"
|
||||
},
|
||||
{
|
||||
"path": "packages/opentelemetry-context-zone/tsconfig.esm.json"
|
||||
},
|
||||
|
|
|
@ -41,6 +41,9 @@
|
|||
{
|
||||
"path": "experimental/packages/otlp-transformer/tsconfig.esnext.json"
|
||||
},
|
||||
{
|
||||
"path": "experimental/packages/sdk-logs/tsconfig.esnext.json"
|
||||
},
|
||||
{
|
||||
"path": "packages/opentelemetry-context-zone/tsconfig.esnext.json"
|
||||
},
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"experimental/packages/otlp-grpc-exporter-base",
|
||||
"experimental/packages/otlp-proto-exporter-base",
|
||||
"experimental/packages/otlp-transformer",
|
||||
"experimental/packages/sdk-logs",
|
||||
"packages/opentelemetry-context-async-hooks",
|
||||
"packages/opentelemetry-context-zone",
|
||||
"packages/opentelemetry-context-zone-peer-dep",
|
||||
|
@ -115,6 +116,9 @@
|
|||
{
|
||||
"path": "experimental/packages/otlp-transformer"
|
||||
},
|
||||
{
|
||||
"path": "experimental/packages/sdk-logs"
|
||||
},
|
||||
{
|
||||
"path": "packages/opentelemetry-context-async-hooks"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue