Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto flush #9

Merged
merged 17 commits into from
May 1, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/optic-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: download deno
uses: denolib/setup-deno@v2
with:
deno-version: v1.30.3
deno-version: v1.32.1
- name: check format
if: matrix.os == 'ubuntu-latest'
run: deno fmt --check
Expand Down
2 changes: 1 addition & 1 deletion deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export {
gray,
red,
yellow,
} from "https://deno.land/std@0.180.0/fmt/colors.ts";
} from "https://deno.land/std@0.181.0/fmt/colors.ts";
4 changes: 2 additions & 2 deletions formatters/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ export type ColorRule = (msg: string) => string;
* A map of coloring rules per log level. Custom log levels may also register
* a new color rule, and existing levels may be updated with new rules too.
*/
const colorRules: Map<Level, ColorRule> = new Map<Level, ColorRule>();
export const colorRules: Map<Level, ColorRule> = new Map<Level, ColorRule>();
colorRules.set(Level.Debug, (msg: string) => gray(msg));
colorRules.set(Level.Info, (msg: string) => blue(msg));
colorRules.set(Level.Warn, (msg: string) => yellow(msg));
colorRules.set(Level.Error, (msg: string) => red(msg));
colorRules.set(Level.Critical, (msg: string) => bold(red(msg)));

export function getColorForLevel(level: number): ColorRule {
export function getColorForLevel(level: Level): ColorRule {
const color: ColorRule | undefined = colorRules.get(level);
return color ? color : (msg: string) => msg;
}
3 changes: 0 additions & 3 deletions formatters/color_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,5 @@ test({
assert(typeof getColorForLevel(Level.Warn) === "function");
assert(typeof getColorForLevel(Level.Error) === "function");
assert(typeof getColorForLevel(Level.Critical) === "function");

// unrecognized level
assert(getColorForLevel(9999)("msg") === "msg");
},
});
16 changes: 9 additions & 7 deletions formatters/tokenReplacer_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import {
} from "../test_deps.ts";
import { TokenReplacer } from "./tokenReplacer.ts";
import { Level } from "../logger/levels.ts";
import { gray } from "../deps.ts";
import { gray, yellow } from "../deps.ts";
import { ValidationError } from "../types.ts";
import { colorRules } from "./color.ts";

const lr = {
msg: "Log Message",
Expand Down Expand Up @@ -159,23 +160,24 @@ test({
test({
name: "Given no color available for level, then no coloring is applied",
fn() {
colorRules.delete(Level.Warn);
const tr = new TokenReplacer().withColor();
lr.level = 99;
lr.level = Level.Warn;
assertEquals(
tr.format(lr),
"2020-06-17T02:24:00.000Z UNKNOWN Log Message The metadata",
// dateTime: new Date("2020-06-17T03:24:00"),
"2020-06-17T02:24:00.000Z Warn Log Message The metadata",
);
colorRules.set(Level.Warn, (msg: string) => yellow(msg));
},
});

test({
name: "Logger name is a valid token",
fn() {
const tr = new TokenReplacer().withFormat("{msg} {logger}");
lr.level = 99;
const tr = new TokenReplacer().withFormat("{msg} (logger: {logger})");
assertEquals(
tr.format(lr),
"Log Message default",
"Log Message (logger: default)",
);
},
});
Expand Down
8 changes: 7 additions & 1 deletion logger/dedupe.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
// Copyright 2020-2023 the optic authors. All rights reserved. MIT license.
import { LogRecord, Stream } from "../types.ts";
import { asString } from "../utils/asString.ts";
import { Level } from "./levels.ts";
import { ImmutableLogRecord } from "./logRecord.ts";
import { LogMetaImpl } from "./meta.ts";

export class Dedupe {
#streams: Stream[];
#meta: LogMetaImpl;
#lastLogRecord: LogRecord = new ImmutableLogRecord(undefined, [], 0, "");
#lastLogRecord: LogRecord = new ImmutableLogRecord(
undefined,
[],
Level.Debug,
"",
);
#lastLogString = "";
#dupeCount = 0;

Expand Down
31 changes: 18 additions & 13 deletions logger/levels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ export enum Level {
Critical = 60,
}

const levelMap = new Map<number, string>();
levelMap.set(10, "Trace");
levelMap.set(20, "Debug");
levelMap.set(30, "Info");
levelMap.set(40, "Warn");
levelMap.set(50, "Error");
levelMap.set(60, "Critical");

const levelNameMap = new Map<string, number>();
const levelMap = new Map<Level, string>();
levelMap.set(Level.Trace, "Trace");
levelMap.set(Level.Debug, "Debug");
levelMap.set(Level.Info, "Info");
levelMap.set(Level.Warn, "Warn");
levelMap.set(Level.Error, "Error");
levelMap.set(Level.Critical, "Critical");

const levelNameMap = new Map<string, Level>();
levelNameMap.set("Trace", Level.Trace);
levelNameMap.set("Debug", Level.Debug);
levelNameMap.set("Info", Level.Info);
Expand All @@ -31,9 +31,9 @@ export function levelToName(level: Level): string {
return levelAsString ? levelAsString : "UNKNOWN";
}

/** Translate string value to Level, or 1 if not found */
export function nameToLevel(name: string): number {
const level: number | undefined = levelNameMap.get(name);
/** Translate string value to Level, or Level.Info if not found */
export function nameToLevel(name: string): Level {
const level: Level | undefined = levelNameMap.get(name);

//try a case insentive match
if (level === undefined) {
Expand All @@ -44,7 +44,12 @@ export function nameToLevel(name: string): number {
}
}

return level === undefined ? 1 : level;
if (!level) {
console.log(`Unknown log level: ${name}, defaulting to 'Info'`);
return Level.Info;
}

return level;
}

/** Returns the length of the longest log level name. This is used when
Expand Down
4 changes: 1 addition & 3 deletions logger/levels_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ test({
assertEquals(levelToName(Level.Warn), "Warn");
assertEquals(levelToName(Level.Error), "Error");
assertEquals(levelToName(Level.Critical), "Critical");
assertEquals(levelToName(0), "UNKNOWN");
assertEquals(levelToName(999), "UNKNOWN");
},
});

Expand All @@ -25,7 +23,7 @@ test({
assertEquals(nameToLevel("Warn"), Level.Warn);
assertEquals(nameToLevel("Error"), Level.Error);
assertEquals(nameToLevel("Critical"), Level.Critical);
assertEquals(nameToLevel("made up level"), 1);
assertEquals(nameToLevel("made up level"), Level.Info);
},
});

Expand Down
8 changes: 4 additions & 4 deletions logger/logger_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,14 @@ test({
});

test({
name: "Logger min level set to 1 if rubbish set for cli argument",
name: "Logger min level set to Level.Info if rubbish set for cli argument",
fn() {
const logger = new class extends Logger {
protected getArgs(): string[] {
return ["minLogLevel=rubbish!"];
}
}();
assertEquals(logger.addStream(new TestStream()).minLogLevel(), 1);
assertEquals(logger.addStream(new TestStream()).minLogLevel(), Level.Info);
},
});

Expand All @@ -166,7 +166,7 @@ test({
});

test({
name: "Logger min level set to 1 if rubbish set for env variable",
name: "Logger min level set to Level.Info if rubbish set for env variable",
fn() {
const logger = new class extends Logger {
protected getEnv(): { get(key: string): string | undefined } {
Expand All @@ -177,7 +177,7 @@ test({
};
}
}();
assertEquals(logger.addStream(new TestStream()).minLogLevel(), 1);
assertEquals(logger.addStream(new TestStream()).minLogLevel(), Level.Info);
},
});

Expand Down
15 changes: 15 additions & 0 deletions logger/profileMeasure_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,21 @@ test({
startOfTestMark,
getMark({ label: "Now", memory: true, ops: false }),
);

assert(
outputWithHeapIncrease.startsWith(
"Measuring 'start of test' -> 'Now', took ",
),
);

assert(
/.*\d+(?:\.\d+)?ms;.*/.test(outputWithHeapIncrease),
);

assert(
outputWithHeapIncrease.includes(""),
);

assert(
/^Measuring 'start of test' -> 'Now', took \d+(?:\.\d+)?ms; heap usage increased \d+\.\d+ [A-Z]{2} to \d+\.\d+ [A-Z]{2}$/
.test(outputWithHeapIncrease),
Expand Down
18 changes: 17 additions & 1 deletion project_utils/pre-commit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,53 @@ rm -f *.log*
rm -f *_log.file_*
rm -rf coverage

echo ''
echo '*** Adding to git'
git add .

echo ''
echo '*** Updating dependencies'
udd ./deps.ts
udd ./test_deps.ts
udd ./streams/fileStream/deps.ts

echo ''
echo '*** Formatting code'
deno fmt

echo ''
echo '*** Linting code ***'
deno lint

#echo '*** Testing code with coverage'
#deno test -A --coverage=cov_profile
#deno coverage cov_profile --lcov > cov_profile/cov.lcov
#genhtml -o cov_profile/html cov_profile/cov.lcov

echo ''
echo '*** Testing code'
deno test -A

echo ''
echo '*** Check license and copyright headers'
deno run --allow-read=. https://deno.land/x/copyright_license_checker@1.1.1/checker.ts project_utils/header_config.json

if [ $? -eq 0 ]; then
echo ''
else
echo '---License and copyright headers are NOT OK---'
echo 'Run: deno run --allow-read=. --allow-write=. https://deno.land/x/copyright_license_checker@1.1.1/updater.ts project_utils/header_config.json'
echo ''
fi

echo '*** Check unstable also compiles'
deno cache --reload --unstable mod.ts
deno cache --reload --unstable streams/fileStream/mod.ts

echo ''
echo '*** Checking git status'
git status

echo ''
echo '#####'
echo "Latest tag is: $(git describe --abbrev=0)"
echo 'To sign a new tag: git tag -s 1.3.13 -m "your tag message"'
Expand Down
15 changes: 15 additions & 0 deletions streams/fileStream/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ The buffer is flushed in the following scenarios:
event)
- `flush()` is called manually. (e.g. `fileStream.flush()`)

### Auto-flushing

You can also setup an auto-flush interval which will flush the buffer on a
regular basis. Example:

```typescript
import { FileStream } from "https://deno.land/x/optic/streams/fileStream/mod.ts";
import { intervalOf } from "https://deno.land/x/optic/utils/timeInterval.ts";

const fileStream = new FileStream("./logFile.txt")
.withAutoFlushEvery(intervalOf(5).seconds());
```

which will flush the logs to the filesystem every 5 seconds.

## Log file initialization

A log file may already exist when your logger initializes. You can control what
Expand Down
6 changes: 3 additions & 3 deletions streams/fileStream/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
export {
basename as posixBasename,
dirname as posixDirname,
} from "https://deno.land/std@0.180.0/path/posix.ts";
} from "https://deno.land/std@0.181.0/path/posix.ts";
export {
basename as win32Basename,
dirname as win32Dirname,
} from "https://deno.land/std@0.180.0/path/win32.ts";
export { BufWriterSync } from "https://deno.land/std@0.180.0/io/buf_writer.ts";
} from "https://deno.land/std@0.181.0/path/win32.ts";
export { BufWriterSync } from "https://deno.land/std@0.181.0/io/buf_writer.ts";
29 changes: 27 additions & 2 deletions streams/fileStream/fileStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TokenReplacer } from "../../formatters/tokenReplacer.ts";
import { Level } from "../../logger/levels.ts";
import { LogFileInitStrategy, RotationStrategy } from "./types.ts";
import { BufWriterSync } from "./deps.ts";
import { TimeInterval } from "../../utils/timeInterval.ts";

/**
* A stream for log messages to go to a file. You may also configure the following:
Expand All @@ -23,7 +24,6 @@ export class FileStream extends BaseStream {
#deferredLogQueue: LogRecord[] = [];
#encoder = new TextEncoder();
#autoFlushId = -1;
#autoFlushInterval = -1;

constructor(filename: string) {
super(new TokenReplacer());
Expand Down Expand Up @@ -53,6 +53,9 @@ export class FileStream extends BaseStream {

destroy(): void {
this.flush();
if (this.#autoFlushId !== -1) {
clearInterval(this.#autoFlushId);
}
super.destroy();
this.#logFile.close();
}
Expand Down Expand Up @@ -133,7 +136,7 @@ export class FileStream extends BaseStream {
return this;
}

/** The maximum size in bytes of the buffer storage before it is flushed. */
/** The maximum size in bytes of the buffer storage before it is flushed (default is 8192, e.g. 8kb)*/
withBufferSize(bytes: number): this {
if (bytes < 0) {
throw new ValidationError("Buffer size cannot be negative");
Expand All @@ -142,6 +145,28 @@ export class FileStream extends BaseStream {
return this;
}

/** Automatically flush the log buffer every `amount` of time. Examples:
* ```typescript
* withAutoFlushEvery(intervalOf(5).seconds())
* withAutoFlushEvery(intervalOf(4).minutes())
* withAutoFlushEvery(intervalOf(3).hours())
* withAutoFlushEvery(intervalOf(2).days())
* ```
*/
withAutoFlushEvery(amount: TimeInterval): this {
if (this.#autoFlushId !== -1) {
clearInterval(this.#autoFlushId);
}

this.#autoFlushId = setInterval(() => {
this.flush();
}, amount.getPeriod() * 1000);

Deno.unrefTimer(this.#autoFlushId);

return this;
}

/** The strategy to take when initializing logs:
* * `"append"` - Reuse log file if it exists, create otherwise
* * `"overwrite"` - Always start with an empty log file, overwriting any existing one
Expand Down