Skip to content

Commit e8da4f0

Browse files
authored
fix(logging-utils): export LogSeverity as value and treat 'true' as '*' alias (#9718)
Exports `LogSeverity` as a runtime value and treats `"true"` as `"*"` ### Changes: - Export `LogSeverity` as a value instead of `export type` in `src/index.ts` - Normalize `nodeFlag === 'true'` to `'*'` in `DebugLogBackendBase` - Added unit tests for runtime `LogSeverity` exports and `"true"` filter normalization.
1 parent f52b715 commit e8da4f0

4 files changed

Lines changed: 122 additions & 26 deletions

File tree

‎core/packages/logging-utils/README.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ This package defines an ad-hoc debug logger utility that can be used as a lightw
66
## Contracts
77
These are logging utilities meant primarily for use as an "adhoc debug logger" inside Google libraries. It's possible to use this outside of that context, but it is currently not supported. (You're on your own.)
88

9-
Additionally, everything about the debug logging is not intended to be a stable interface - you can't rely on messages to remain the same, or to continue to exist from one version to the next. For now, it is meant to be a tool for local usage by users, to get the equivalent of "printf debugging" output. In the future, it may tie into more things (OpenTelemetry Logging, etc). Structured log output is possible for vacuuming into Cloud Logging.
10-
11-
This npm package itself is intended to follow semver standards for its own interfaces, but no guarantees are made for supported time windows, etc.
9+
* **Stable Package API (SemVer 1.x):** The exported programmatic interface—`log()`, `placeholder`, `LogFields`, `LogSeverity`, `AdhocDebugLogFunction` (including `.on('log', ...)` and `.sublog()`), `setBackend()`, `getNodeBackend()`, `getStructuredBackend()`, and `env.nodeEnables` (`GOOGLE_SDK_NODE_LOGGING`)—is a strict SemVer-stable contract used across `google-gax`, `google-auth-library`, `gcp-metadata`, and `@google-cloud/*`.
10+
* **Non-Contractual Log Output:** Exact human-readable log message strings and the addition of new diagnostic attributes in `LogFields.other` may evolve across minor versions so customers do not rely on regex-parsing prose messages. For now, it is meant to be a tool for local usage by users, to get the equivalent of "printf debugging" output. In the future, it may tie into more things (OpenTelemetry Logging, etc). Structured log output is possible for vacuuming into Cloud Logging.
1211

1312
## Usage
1413
The user interface for this logging is very similar to Node's built-in logging. The primary unit of logging is called a "system", and some libraries may have sub-units called "subsystems". You separate the system and subsystem with a colon, like "pubsub:leasing" or similar. Wildcards may be used (e.g. "pubsub:*"), and multiple of these system/system:subsystem pairs may be listed out, separated by commas. For generated GAPIC libraries, the "system" will generally be the part of the package name specific to the library (e.g. `@google-cloud/translate` will be `translate`).

‎core/packages/logging-utils/src/index.ts‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,5 @@
1414

1515
export * from './logging-utils';
1616
// 🌟 ADDED EXPORT FOR TYPES 🌟
17-
export type {
18-
LogFields,
19-
LogSeverity,
20-
AdhocDebugLogFunction,
21-
DebugLogBackend,
22-
} from './types';
17+
export {LogSeverity} from './types';
18+
export type {LogFields, AdhocDebugLogFunction, DebugLogBackend} from './types';

‎core/packages/logging-utils/src/logging-utils.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export abstract class DebugLogBackendBase implements DebugLogBackend {
123123
// Look for the Node config variable for what systems to enable. We'll store
124124
// these for the log method below, which will call setFilters() once.
125125
let nodeFlag = process.env[env.nodeEnables] ?? '*';
126-
if (nodeFlag === 'all') {
126+
if (nodeFlag === 'all' || nodeFlag === 'true') {
127127
nodeFlag = '*';
128128
}
129129
this.filters = nodeFlag.split(',');

‎core/packages/logging-utils/test/logging-utils.ts‎

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,19 @@ import {describe, it} from 'mocha';
1616
import * as assert from 'assert';
1717
import * as sinon from 'sinon';
1818

19-
import * as al from '../src/logging-utils';
20-
import * as alTypes from '../src/types';
19+
import * as al from '../src/index';
2120

2221
interface TestLog {
2322
namespace: string;
24-
fields: alTypes.LogFields;
23+
fields: al.LogFields;
2524
args: unknown[];
2625
}
2726

2827
class TestSink extends al.DebugLogBackendBase {
2928
logs: TestLog[] = [];
3029

3130
makeLogger(namespace: string): any {
32-
return (fields: alTypes.LogFields, ...args: unknown[]) => {
31+
return (fields: al.LogFields, ...args: unknown[]) => {
3332
this.logs.push({namespace, fields, args});
3433
};
3534
}
@@ -57,13 +56,29 @@ describe('adhoc-logging', () => {
5756
});
5857

5958
it('obeys "all" as an alias for "*"', () => {
60-
al.setBackend(sink);
61-
sink.reset();
62-
6359
process.env[al.env.nodeEnables] = 'all';
60+
const backend = new TestSink();
61+
al.setBackend(backend);
62+
assert.deepStrictEqual(backend.filters, ['*']);
6463
const logger = al.log(system);
6564
logger({}, 'foo');
66-
assert.deepStrictEqual(sink.logs, [
65+
assert.deepStrictEqual(backend.logs, [
66+
{
67+
namespace: system,
68+
fields: {},
69+
args: ['foo'],
70+
},
71+
]);
72+
});
73+
74+
it('obeys "true" as an alias for "*"', () => {
75+
process.env[al.env.nodeEnables] = 'true';
76+
const backend = new TestSink();
77+
al.setBackend(backend);
78+
assert.deepStrictEqual(backend.filters, ['*']);
79+
const logger = al.log(system);
80+
logger({}, 'foo');
81+
assert.deepStrictEqual(backend.logs, [
6782
{
6883
namespace: system,
6984
fields: {},
@@ -121,13 +136,13 @@ describe('adhoc-logging', () => {
121136
});
122137

123138
it('logs with fields', () => {
124-
logger({severity: alTypes.LogSeverity.INFO}, 'test log', 5, {
139+
logger({severity: al.LogSeverity.INFO}, 'test log', 5, {
125140
other: 'foo',
126141
});
127142
assert.deepStrictEqual(sink.logs, [
128143
{
129144
namespace: system,
130-
fields: {severity: alTypes.LogSeverity.INFO},
145+
fields: {severity: al.LogSeverity.INFO},
131146
args: ['test log', 5, {other: 'foo'}],
132147
},
133148
]);
@@ -141,22 +156,22 @@ describe('adhoc-logging', () => {
141156
assert.deepStrictEqual(sink.logs, [
142157
{
143158
namespace: system,
144-
fields: {severity: alTypes.LogSeverity.INFO},
159+
fields: {severity: al.LogSeverity.INFO},
145160
args: ['test info'],
146161
},
147162
{
148163
namespace: system,
149-
fields: {severity: alTypes.LogSeverity.WARNING},
164+
fields: {severity: al.LogSeverity.WARNING},
150165
args: ['test warn'],
151166
},
152167
{
153168
namespace: system,
154-
fields: {severity: alTypes.LogSeverity.DEBUG},
169+
fields: {severity: al.LogSeverity.DEBUG},
155170
args: ['test debug'],
156171
},
157172
{
158173
namespace: system,
159-
fields: {severity: alTypes.LogSeverity.ERROR},
174+
fields: {severity: al.LogSeverity.ERROR},
160175
args: ['test error'],
161176
},
162177
]);
@@ -190,13 +205,13 @@ describe('adhoc-logging', () => {
190205
it('allows receiving logs', () => {
191206
const received = [{fields: {}, args: [] as unknown[]}];
192207
received.pop();
193-
logger.on('log', (fields: alTypes.LogFields, args: unknown[]) => {
208+
logger.on('log', (fields: al.LogFields, args: unknown[]) => {
194209
received.push({fields, args});
195210
});
196211
logger.info('cool cool');
197212
assert.deepStrictEqual(received, [
198213
{
199-
fields: {severity: alTypes.LogSeverity.INFO},
214+
fields: {severity: al.LogSeverity.INFO},
200215
args: ['cool cool'],
201216
},
202217
]);
@@ -297,4 +312,90 @@ describe('adhoc-logging', () => {
297312
]);
298313
});
299314
});
315+
316+
describe('Public entrypoint contract (src/index.ts)', () => {
317+
it('exports LogSeverity as a runtime enum from index', () => {
318+
assert.strictEqual(al.LogSeverity.DEFAULT, 'DEFAULT');
319+
assert.strictEqual(al.LogSeverity.DEBUG, 'DEBUG');
320+
assert.strictEqual(al.LogSeverity.INFO, 'INFO');
321+
assert.strictEqual(al.LogSeverity.WARNING, 'WARNING');
322+
assert.strictEqual(al.LogSeverity.ERROR, 'ERROR');
323+
});
324+
325+
it('exports required functions, classes, and constants on the public entrypoint', () => {
326+
assert.strictEqual(typeof al.log, 'function');
327+
assert.strictEqual(typeof al.placeholder, 'function');
328+
assert.strictEqual(typeof al.setBackend, 'function');
329+
assert.strictEqual(typeof al.getNodeBackend, 'function');
330+
assert.strictEqual(typeof al.getDebugBackend, 'function');
331+
assert.strictEqual(typeof al.getStructuredBackend, 'function');
332+
assert.strictEqual(typeof al.AdhocDebugLogger, 'function');
333+
assert.strictEqual(typeof al.DebugLogBackendBase, 'function');
334+
assert.strictEqual(typeof al.env, 'object');
335+
assert.strictEqual(al.env.nodeEnables, 'GOOGLE_SDK_NODE_LOGGING');
336+
});
337+
338+
it('exports placeholder conforming to AdhocDebugLogFunction contract', () => {
339+
const fn: al.AdhocDebugLogFunction = al.placeholder;
340+
assert.strictEqual(typeof fn.debug, 'function');
341+
assert.strictEqual(typeof fn.info, 'function');
342+
assert.strictEqual(typeof fn.warn, 'function');
343+
assert.strictEqual(typeof fn.error, 'function');
344+
assert.strictEqual(typeof fn.sublog, 'function');
345+
assert.strictEqual(typeof fn.on, 'function');
346+
assert.ok(fn.instance instanceof al.AdhocDebugLogger);
347+
348+
// Invoking placeholder methods when disabled is a safe no-op.
349+
al.setBackend(null);
350+
assert.doesNotThrow(() => {
351+
fn({severity: al.LogSeverity.DEBUG, other: {key: 'val'}}, 'noop');
352+
fn.debug('noop');
353+
fn.info('noop');
354+
fn.warn('noop');
355+
fn.error('noop');
356+
assert.strictEqual(fn.sublog('child'), al.placeholder);
357+
});
358+
});
359+
360+
it('preserves AdhocDebugLogFunction, LogFields, and DebugLogBackend contracts via log()', () => {
361+
al.setBackend(sink);
362+
sink.reset();
363+
process.env[al.env.nodeEnables] = '*';
364+
365+
const logger: al.AdhocDebugLogFunction = al.log('contract-system');
366+
assert.strictEqual(typeof logger, 'function');
367+
assert.strictEqual(typeof logger.debug, 'function');
368+
assert.strictEqual(typeof logger.info, 'function');
369+
assert.strictEqual(typeof logger.warn, 'function');
370+
assert.strictEqual(typeof logger.error, 'function');
371+
assert.strictEqual(typeof logger.sublog, 'function');
372+
assert.strictEqual(typeof logger.on, 'function');
373+
assert.ok(logger.instance instanceof al.AdhocDebugLogger);
374+
375+
const fields: al.LogFields = {
376+
severity: al.LogSeverity.INFO,
377+
telemetryTraceId: 'trace-123',
378+
telemetrySpanId: 'span-456',
379+
other: {'rpc.system': 'grpc'},
380+
};
381+
logger(fields, 'contract message %s', 'arg1');
382+
383+
assert.deepStrictEqual(sink.logs, [
384+
{
385+
namespace: 'contract-system',
386+
fields,
387+
args: ['contract message %s', 'arg1'],
388+
},
389+
]);
390+
391+
const nodeBackend: al.DebugLogBackend = al.getNodeBackend();
392+
assert.strictEqual(typeof nodeBackend.log, 'function');
393+
assert.strictEqual(typeof nodeBackend.setFilters, 'function');
394+
395+
const structuredBackend: al.DebugLogBackend =
396+
al.getStructuredBackend(nodeBackend);
397+
assert.strictEqual(typeof structuredBackend.log, 'function');
398+
assert.strictEqual(typeof structuredBackend.setFilters, 'function');
399+
});
400+
});
300401
});

0 commit comments

Comments
 (0)