Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions unitTests/utility/logging/fixtures/noConfigLogging.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

// The no-config logging window — install, and any host with no harperdb-config.yaml.
// initLogSettings() picks that branch at module load, from the machine's own config resolution,
// which is why this is a child: the caller spawns it with ROOTPATH at a directory holding no
// config, and the branch is then taken on an installed machine and a bare one alike.

const { warn } = require('#src/utility/logging/harper_logger');

warn('no-config stream check');

// stdioLogging() stashes its 'error' listener on the stream it guards, and the fallback branch
// returns before the call at the end of initLogSettings().
const guarded = (stream) => typeof stream.harperStdioErrorHandler === 'function';
process.stdout.write(`stdout-guard=${guarded(process.stdout)} stderr-guard=${guarded(process.stderr)}\n`);
37 changes: 36 additions & 1 deletion unitTests/utility/logging/harper_logger.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';

const assert = require('node:assert');
const { EventEmitter } = require('node:events');
const { EventEmitter, once } = require('node:events');
const { spawn } = require('node:child_process');
const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;
Expand Down Expand Up @@ -192,6 +193,40 @@ describe('Test harper_logger module', () => {
expect(log_file_path).to.eql(path.join(TEST_LOG_DIR, 'hdb.log'));
});

// The install window, and any host with no harperdb-config.yaml. `log_to_file` is false
// there, so the streams are the only sink left; createLogger() shadows the module-level
// `logToStdstreams` with its own option, and a call that omits it drops the line entirely
// rather than writing it anywhere (harper#2364, where the Windows gate caught it as a
// warning-cadence test counting 0 of 2).
it('writes to the std streams, guarded, when there is no config to read', async function () {
this.timeout(30000);
const noConfigRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harper-no-config-'));
afterThisTest.push(() => {
try {
fs.removeSync(noConfigRoot);
} catch {}
});

// A ROOTPATH naming a directory with no config reaches the fallback either way: with boot
// properties present the config read throws ENOENT, and without them initLogSettings()
// only swallows that failure when ROOTPATH *does* hold a config. LOGGING_LEVEL is pinned
// for the same reason — that branch reads it from the environment, and the fixture logs
// at the default threshold.
const child = spawn(process.execPath, [require.resolve('./fixtures/noConfigLogging.cjs')], {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, ROOTPATH: noConfigRoot, LOGGING_LEVEL: 'warn' },
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (chunk) => (stdout += chunk));
child.stderr.on('data', (chunk) => (stderr += chunk));
const [code] = await once(child, 'close');

assert.equal(code, 0, `fixture exited ${code}: ${stderr}`);
assert.match(stderr, /no-config stream check/);
assert.match(stdout, /stdout-guard=true stderr-guard=true/);
});

it('Test that if error code is not ENOENT error is handled correctly', () => {
// This asserts the path where there is nothing to fall back to, so ROOTPATH has to be
// absent: initLogSettings() deliberately SWALLOWS a failure to read the boot properties
Expand Down
9 changes: 8 additions & 1 deletion utility/logging/harper_logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,10 +545,17 @@ export function initLogSettings(forceInit = false) {

logLevel = logLevel === undefined ? defaultLevel : logLevel;

mainLogger = createLogger({ level: logLevel });
// createLogger shadows the module-level `logToStdstreams` with this option, so omitting it
// drops every log written before a config exists rather than routing it to the streams.
mainLogger = createLogger({ level: logLevel, stdStreams: logToStdstreams });
// setup the external logger
externalLogger = mainLogger.forComponent('external');
externalLogger.tag = null; // don't tag by default
// The streams are this branch's only sink, so it needs the same EPIPE/EIO listeners the
// configured path installs below: `harper install | head -1` closes the reader, and the
// async error would otherwise land on a stream with none and take the install down. The
// guard's write override is inert here, because `log_to_file` is false.
stdioLogging();
return;
}

Expand Down
Loading