Skip to content

Commit 053c04e

Browse files
committed
PR comments - examples about different log levels
1 parent 4427728 commit 053c04e

12 files changed

Lines changed: 262 additions & 376 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,5 @@ The developers gratefully acknowledge their research support:
222222
## Logging
223223

224224
This library uses [loglevel](https://github.com/pimterry/loglevel) for logging. By default, the log level is set to "warn". You can change the log level by setting the `LOG_LEVEL` environment variable or by using the `setLevel` method in your code.
225+
226+
Named loggers (for example `validationLog`) can be turned up or down independently of the root log and of each other. That lets you enable validation messages without changing general logging, or silence validation in tests while keeping other logs. Use `getLogger(name)` for custom loggers, or the built-in `validationLog` (see `src/log.js`). Set level with e.g. `validationLog.setLevel("warn")` or `validationLog.setLevel(5)` for silent. Examples in this repo: validation truncation is logged at error level in [`src/DicomMetaDictionary.js`](src/DicomMetaDictionary.js) (search for `validationLog.error`), and a test that suppresses that output by setting the validation logger to silent only for that test is in [`test/data.test.js`](test/data.test.js) (search for `test_code_string_vr_truncated`).

jest.setup.js

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,20 @@
1-
const { Console } = require("console");
2-
const nodeConsole = new Console(process.stdout, process.stderr);
3-
4-
// Mock loglevel
5-
const createMockLogger = () => {
6-
const logger = {
7-
trace: jest.fn(),
8-
debug: jest.fn(),
9-
info: jest.fn(),
10-
warn: jest.fn(),
11-
error: jest.fn(),
12-
setLevel: jest.fn()
1+
// Jest setup - uses real loglevel, writing to stdout to avoid stack traces from console.warn/error.
2+
// Format: loggerName [LEVEL] message
3+
// Log level is controlled by LOG_LEVEL env (default "warn" in src/log.js).
4+
5+
const loglevel = require("loglevel");
6+
const { inspect } = require("util");
7+
8+
loglevel.methodFactory = function (methodName, _level, loggerName) {
9+
return function (...args) {
10+
const message = args
11+
.map((a) => (typeof a === "string" ? a : inspect(a)))
12+
.join(" ");
13+
const name =
14+
loggerName != null && loggerName !== ""
15+
? String(loggerName)
16+
: "log";
17+
process.stdout.write(`${name} [${methodName.toUpperCase()}] ${message}\n`);
1318
};
14-
15-
// Modify warn to print directly to stdout instead of console.warn
16-
logger.warn.mockImplementation((...args) => {
17-
// This uses Node's real util.inspect internally
18-
nodeConsole.warn("[warn]", ...args);
19-
});
20-
21-
return logger;
2219
};
23-
24-
const mockLog = createMockLogger();
25-
26-
mockLog.getLogger = jest.fn(name => {
27-
const namedLogger = createMockLogger();
28-
namedLogger.name = name;
29-
return namedLogger;
30-
});
31-
32-
jest.mock("loglevel", () => mockLog);
33-
34-
// Optional global access for assertions
35-
global.mockLog = mockLog;
36-
37-
// Override console.warn to remove stack traces
38-
console.warn = nodeConsole.warn;
39-
console.time = nodeConsole.time;
40-
console.timeEnd = nodeConsole.timeEnd;
20+
loglevel.rebuild();

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dcmjs",
3-
"version": "0.49.2",
3+
"version": "0.50.2",
44
"description": "Javascript implementation of DICOM manipulation",
55
"main": "build/dcmjs.js",
66
"module": "build/dcmjs.es.js",
@@ -53,7 +53,7 @@
5353
"@rollup/plugin-json": "^6.1.0",
5454
"@rollup/plugin-node-resolve": "^15.2.3",
5555
"@rollup/plugin-replace": "^6.0.1",
56-
"@rollup/plugin-terser": "^0.4.4",
56+
"@rollup/plugin-terser": "^0.4.4",
5757
"acorn": "^7.1.0",
5858
"acorn-jsx": "^5.2.0",
5959
"eslint": "^9.30.1",
@@ -72,7 +72,7 @@
7272
"adm-zip": "^0.5.10",
7373
"gl-matrix": "^3.1.0",
7474
"lodash.clonedeep": "^4.5.0",
75-
"loglevel": "^1.8.1",
75+
"loglevel": "^1.9.2",
7676
"ndarray": "^1.0.19",
7777
"pako": "^2.0.4"
7878
},

src/AsyncDicomReader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { DicomMetaDictionary } from "./DicomMetaDictionary";
1818
import { DicomMetadataListener } from "./utilities/DicomMetadataListener.js";
1919
import { log } from "./log.js";
2020

21-
const readLog = log.getLogger("AsyncDicomReader");
21+
const readLog = log.getLogger("dcmjs.AsyncDicomReader");
2222

2323
/**
2424
* This is an asynchronous binary DICOM reader.

src/DicomMetaDictionary.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { dictionary } from "./dictionary.fast.js";
22
import { getAllStandardTagEntries } from "./dicom.lookup.js";
3-
import log from "./log.js";
3+
import log, { validationLog } from "./log.js";
44
import addAccessors from "./utilities/addAccessors";
55
import { ValueRepresentation } from "./ValueRepresentation";
66

@@ -278,8 +278,13 @@ export class DicomMetaDictionary {
278278
}
279279

280280
if (value.length > maxLength) {
281-
log.warn(
282-
`Truncating value ${value} of ${naturalName} because it is longer than ${maxLength}`
281+
validationLog.error(
282+
"Truncating value",
283+
value,
284+
"of",
285+
naturalName,
286+
"because it is longer than",
287+
maxLength
283288
);
284289
return value.slice(0, maxLength);
285290
} else {
@@ -294,7 +299,7 @@ export class DicomMetaDictionary {
294299
} else {
295300
const validMetaNames = ["_vrMap", "_meta"];
296301
if (validMetaNames.indexOf(name) == -1) {
297-
log.warn(
302+
validationLog.info(
298303
"Unknown name in dataset",
299304
name,
300305
":",

src/ValueRepresentation.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,13 @@ class ValueRepresentation {
197197
value: this.defaultValue
198198
};
199199
if (this.maxLength != length)
200-
log.error(
201-
"Invalid length for fixed length tag, vr " +
202-
this.type +
203-
", length " +
204-
this.maxLength +
205-
" != " +
206-
length
200+
validationLog.warn(
201+
"Invalid length for fixed length tag, vr",
202+
this.type,
203+
", expected length",
204+
this.maxLength,
205+
"!= actual",
206+
length
207207
);
208208
}
209209
let rawValue = this.readBytes(stream, length, syntax);
@@ -350,13 +350,13 @@ class ValueRepresentation {
350350
if (type == "ox") {
351351
// TODO: determine VR based on context (could be 1 byte pixel data)
352352
// https://github.com/dgobbi/vtk-dicom/issues/38
353-
validationLog.error("Invalid vr type", type, "- using OW");
353+
validationLog.info("Invalid vr type", type, "- using OW");
354354
vr = VRinstances["OW"];
355355
} else if (type == "xs") {
356-
validationLog.error("Invalid vr type", type, "- using US");
356+
validationLog.info("Invalid vr type", type, "- using US");
357357
vr = VRinstances["US"];
358358
} else {
359-
validationLog.error("Invalid vr type", type, "- using UN");
359+
validationLog.info("Invalid vr type", type, "- using UN");
360360
vr = VRinstances["UN"];
361361
}
362362
}

src/adapters/Cornerstone/Segmentation_4X.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import log from "../../log.js";
1+
import log, { validationLog } from "../../log.js";
22
import ndarray from "ndarray";
33
import { BitArray } from "../../bitArray.js";
44
import { datasetToBlob } from "../../datasetToBlob.js";
@@ -593,10 +593,10 @@ function checkSEGsOverlapping(
593593
);
594594

595595
if (!imageId) {
596-
console.warn(
597-
"Image not present in stack, can't import frame : " +
598-
frameSegment +
599-
"."
596+
validationLog.warn(
597+
"Image not present in stack, can't import frame :",
598+
frameSegment,
599+
"."
600600
);
601601
continue;
602602
}
@@ -783,10 +783,10 @@ function insertOverlappingPixelDataPlanar(
783783
);
784784

785785
if (!imageId) {
786-
console.warn(
787-
"Image not present in stack, can't import frame : " +
788-
i +
789-
"."
786+
validationLog.warn(
787+
"Image not present in stack, can't import frame :",
788+
i,
789+
"."
790790
);
791791
continue;
792792
}
@@ -963,8 +963,10 @@ function insertPixelDataPlanar(
963963
);
964964

965965
if (!imageId) {
966-
console.warn(
967-
"Image not present in stack, can't import frame : " + i + "."
966+
validationLog.warn(
967+
"Image not present in stack, can't import frame :",
968+
i,
969+
"."
968970
);
969971
continue;
970972
}

src/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ registerPrivatesModule(privateData);
1414
import { Tag } from "./Tag.js";
1515
import { ValueRepresentation } from "./ValueRepresentation.js";
1616
import { Colors } from "./colors.js";
17-
import log from "./log.js";
17+
import { log, dcmjsLog, validationLog } from "./log.js";
1818

1919
import { AsyncDicomReader } from "./AsyncDicomReader.js";
2020

@@ -107,6 +107,8 @@ const dcmjs = {
107107
sr,
108108
utilities,
109109
log,
110+
validationLog,
111+
dcmjsLog,
110112
anonymizer,
111113
async
112114
};
@@ -127,7 +129,9 @@ export {
127129
normalizers,
128130
sr,
129131
utilities,
130-
log
132+
log,
133+
dcmjsLog,
134+
validationLog
131135
};
132136

133137
export { dcmjs as default };

src/log.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1-
import log from "loglevel";
1+
import loglevel from "loglevel";
2+
3+
/** Root logger: use noConflict() in browser to restore window.log, then use same logger */
4+
const log =
5+
typeof loglevel.noConflict === "function"
6+
? loglevel.noConflict()
7+
: loglevel;
28

39
log.setLevel(process.env.LOG_LEVEL || "warn");
410

11+
/**
12+
* DICOM validation logger.
13+
* - error: something is an error and can't be recovered from
14+
* - warn: serious issue but execution can continue
15+
* - info: something not quite right but won't cause immediate problems
16+
*/
517
const validationLog = log.getLogger("validation.dcmjs");
18+
const dcmjsLog = log.getLogger("dcmjs");
619

7-
export { log, validationLog };
20+
export { log, validationLog, dcmjsLog };
821
export default log;

test/data.test.js

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import fsPromises from "fs/promises";
44
import path from "path";
55
import { WriteBufferStream } from "../src/BufferStream";
66
import dcmjs from "../src/index.js";
7-
import { log } from "./../src/log.js";
7+
import { log, validationLog } from "./../src/log.js";
88
import { getTestDataset, getZippedTestDataset } from "./testUtils.js";
99

1010
import { promisify } from "util";
@@ -591,17 +591,23 @@ it("test_custom_dictionary", () => {
591591

592592
it("test_code_string_vr_truncated", () => {
593593
// Create a dataset with a CS value that exceeds the 16-character limit and gets truncated
594-
const testDataset = {
595-
Modality: "MAGNETICRESONANCE"
596-
};
597-
598-
const denaturalizedDataset =
599-
DicomMetaDictionary.denaturalizeDataset(testDataset);
600-
601-
expect(denaturalizedDataset["00080060"].vr).toEqual("CS");
602-
expect(denaturalizedDataset["00080060"].Value[0]).toEqual(
603-
"MAGNETICRESONANC"
604-
);
594+
const savedLevel = validationLog.getLevel();
595+
validationLog.setLevel(5); // silent for this test to avoid validation error output
596+
try {
597+
const testDataset = {
598+
Modality: "MAGNETICRESONANCE"
599+
};
600+
601+
const denaturalizedDataset =
602+
DicomMetaDictionary.denaturalizeDataset(testDataset);
603+
604+
expect(denaturalizedDataset["00080060"].vr).toEqual("CS");
605+
expect(denaturalizedDataset["00080060"].Value[0]).toEqual(
606+
"MAGNETICRESONANC"
607+
);
608+
} finally {
609+
validationLog.setLevel(savedLevel);
610+
}
605611
});
606612

607613
it("test_date_time_vr_range_matching_not_truncated", () => {

0 commit comments

Comments
 (0)