Skip to content

Commit

Permalink
remove 1 type cast
Browse files Browse the repository at this point in the history
  • Loading branch information
davemarco committed Sep 30, 2024
1 parent dc9e533 commit 114af1d
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 87 deletions.
119 changes: 60 additions & 59 deletions new-log-viewer/src/services/decoders/JsonlDecoder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
JsonlDecoderOptionsType,
LogEventCount,
} from "../../../typings/decoders";
import {Dayjs} from "dayjs";
import {Formatter} from "../../../typings/formatters";
import {JsonValue} from "../../../typings/js";
import {
JsonLogEvent,
INVALID_TIMESTAMP_VALUE,
LOG_LEVEL,
LogLevelFilter,
Expand All @@ -18,7 +20,6 @@ import {
convertToDayjsTimestamp,
convertToLogLevelValue,
isJsonObject,
JsonLogEvent,
} from "./utils";


Expand Down Expand Up @@ -63,7 +64,7 @@ class JsonlDecoder implements Decoder {
}

setLogLevelFilter (logLevelFilter: LogLevelFilter): boolean {
this.#filterLogs(logLevelFilter);
this.#filterLogEvents(logLevelFilter);

return true;
}
Expand All @@ -90,16 +91,12 @@ class JsonlDecoder implements Decoder {
endIdx: number,
useFilter: boolean,
): Nullable<DecodeResultType[]> {
if (useFilter && null === this.#filteredLogEventMap) {
if (null === this.#filteredLogEventMap && useFilter) {
return null;
}

// Prevents typescript potential null warning.
const filteredLogEventMap: number[] = this.#filteredLogEventMap as number[];


const length: number = useFilter ?
filteredLogEventMap.length :
const length: number = (useFilter && null !== this.#filteredLogEventMap) ?
this.#filteredLogEventMap.length :
this.#logEvents.length;

if (0 > beginIdx || length < endIdx) {
Expand All @@ -111,51 +108,16 @@ class JsonlDecoder implements Decoder {
// Explicit cast since typescript thinks `#filteredLogEventMap[i]`
// can be undefined, but it shouldn't be since we performed a bounds check at the
// beginning of the method.
const logEventIdx: number = useFilter ?
(filteredLogEventMap[i] as number) :
const logEventIdx: number = (useFilter && null !== this.#filteredLogEventMap) ?
(this.#filteredLogEventMap[i] as number) :
i;

results.push(this.#getDecodeResult(logEventIdx));
results.push(this.#decodeLogEvent(logEventIdx));
}

return results;
}

/**
* Decodes a log event into a `DecodeResultType`.
*
* @param logEventIdx
* @return The decoded log event.
*/
#getDecodeResult = (logEventIdx: number): DecodeResultType => {
let timestamp: number;
let message: string;
let logLevel: LOG_LEVEL;

// eslint-disable-next-line no-warning-comments
// TODO We could probably optimize this to avoid checking `#invalidLogEventIdxToRawLine` on
// every iteration.
if (this.#invalidLogEventIdxToRawLine.has(logEventIdx)) {
timestamp = INVALID_TIMESTAMP_VALUE;
message = `${this.#invalidLogEventIdxToRawLine.get(logEventIdx)}\n`;
logLevel = LOG_LEVEL.NONE;
} else {
// Explicit cast since typescript thinks `#logEvents[logEventIdx]` can be undefined,
// but it shouldn't be since the index comes from a class-internal filter.
const logEvent = this.#logEvents[logEventIdx] as JsonLogEvent;
logLevel = logEvent.level;
message = this.#formatter.formatLogEvent(logEvent);
timestamp = logEvent.timestamp.valueOf();
}

return [
message,
timestamp,
logLevel,
logEventIdx + 1,
];
};

/**
* Parses each line from the data array and buffers it internally.
*
Expand Down Expand Up @@ -191,29 +153,32 @@ class JsonlDecoder implements Decoder {
* @param line
*/
#parseJson (line: string) {
let fields: JsonValue;
let level: LOG_LEVEL;
let timestamp: Dayjs;
try {
const fields = JSON.parse(line) as JsonValue;
fields = JSON.parse(line) as JsonValue;
if (false === isJsonObject(fields)) {
throw new Error("Unexpected non-object.");
}
this.#logEvents.push({
fields: fields,
level: convertToLogLevelValue(fields[this.#logLevelKey]),
timestamp: convertToDayjsTimestamp(fields[this.#timestampKey]),
});
level = convertToLogLevelValue(fields[this.#logLevelKey]);
timestamp = convertToDayjsTimestamp(fields[this.#timestampKey]);
} catch (e) {
if (0 === line.length) {
return;
}
console.error(e, line);
const currentLogEventIdx = this.#logEvents.length;
this.#invalidLogEventIdxToRawLine.set(currentLogEventIdx, line);
this.#logEvents.push({
fields: {},
level: LOG_LEVEL.NONE,
timestamp: convertToDayjsTimestamp(INVALID_TIMESTAMP_VALUE),
});
fields = {};
level = LOG_LEVEL.NONE;
timestamp = convertToDayjsTimestamp(INVALID_TIMESTAMP_VALUE);
}
this.#logEvents.push({
fields,
level,
timestamp,
});
}

/**
Expand All @@ -222,7 +187,7 @@ class JsonlDecoder implements Decoder {
*
* @param logLevelFilter
*/
#filterLogs (logLevelFilter: LogLevelFilter) {
#filterLogEvents (logLevelFilter: LogLevelFilter) {
if (null === logLevelFilter) {
this.#filteredLogEventMap = null;
return;

Check warning on line 193 in new-log-viewer/src/services/decoders/JsonlDecoder/index.ts

View workflow job for this annotation

GitHub Actions / lint-check

Expected blank line before this statement
Expand All @@ -235,6 +200,42 @@ class JsonlDecoder implements Decoder {
}
});
}

/**
* Decodes a log event into a `DecodeResultType`.
*
* @param logEventIdx
* @return The decoded log event.
*/
#decodeLogEvent = (logEventIdx: number): DecodeResultType => {
let timestamp: number;
let message: string;
let logLevel: LOG_LEVEL;

// eslint-disable-next-line no-warning-comments
// TODO We could probably optimize this to avoid checking `#invalidLogEventIdxToRawLine` on
// every iteration.
if (this.#invalidLogEventIdxToRawLine.has(logEventIdx)) {
timestamp = INVALID_TIMESTAMP_VALUE;
message = `${this.#invalidLogEventIdxToRawLine.get(logEventIdx)}\n`;
logLevel = LOG_LEVEL.NONE;
} else {
// Explicit cast since typescript thinks `#logEvents[logEventIdx]` can be undefined,
// but it shouldn't be since the index comes from a class-internal filter.
const logEvent = this.#logEvents[logEventIdx] as JsonLogEvent;
logLevel = logEvent.level;
message = this.#formatter.formatLogEvent(logEvent);
timestamp = logEvent.timestamp.valueOf();
}

return [
message,
timestamp,
logLevel,
logEventIdx + 1,
];
};
}


export default JsonlDecoder;
41 changes: 17 additions & 24 deletions new-log-viewer/src/services/decoders/JsonlDecoder/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,35 @@ import {
// eslint-disable-next-line import/no-named-as-default-member
dayjs.extend(utc);

interface JsonLogEvent {
timestamp: Dayjs,
level: LOG_LEVEL,
fields: JsonObject
}

/**
* Determines whether the given value is a `JsonObject` and applies a TypeScript narrowing
* conversion if so.
*
* Reference: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates
*
* @param fields
* @return A TypeScript type predicate indicating whether `fields` is a `JsonObject`.
* @param value
* @return A TypeScript type predicate indicating whether `value` is a `JsonObject`.
*/
const isJsonObject = (fields: JsonValue): fields is JsonObject => {
return "object" === typeof fields && null !== fields;
const isJsonObject = (value: JsonValue): value is JsonObject => {
return "object" === typeof value && null !== value;
};

/**
* Converts a field into a log level if possible.
*
* @param logLevelField
* @param field
* @return The log level or `LOG_LEVEL.NONE` if the field couldn't be converted.
*/
const convertToLogLevelValue = (logLevelField: JsonValue | undefined): LOG_LEVEL => {
const convertToLogLevelValue = (field: JsonValue | undefined): LOG_LEVEL => {
let logLevelValue = LOG_LEVEL.NONE;

if ("undefined" === typeof logLevelField) {
if ("undefined" === typeof field) {
return logLevelValue;
}

const logLevelName = "object" === typeof logLevelField ?
JSON.stringify(logLevelField) :
String(logLevelField);
const logLevelName = "object" === typeof field ?
JSON.stringify(field) :
String(field);

const uppercaseLogLevelName = logLevelName.toUpperCase();
if (uppercaseLogLevelName in LOG_LEVEL) {
Expand All @@ -61,27 +55,27 @@ const convertToLogLevelValue = (logLevelField: JsonValue | undefined): LOG_LEVEL
/**
* Converts a field into a dayjs timestamp if possible.
*
* @param timestampField
* @param field
* @return The field as a dayjs timestamp or `dayjs.utc(INVALID_TIMESTAMP_VALUE)` if:
* - the timestamp key doesn't exist in the log.
* - the timestamp's value is an unsupported type.
* - the timestamp's value is not a valid dayjs timestamp.
*/
const convertToDayjsTimestamp = (timestampField: JsonValue | undefined): dayjs.Dayjs => {
const convertToDayjsTimestamp = (field: JsonValue | undefined): dayjs.Dayjs => {
// If the field is an invalid type, then set the timestamp to `INVALID_TIMESTAMP_VALUE`.
if (("string" !== typeof timestampField &&
"number" !== typeof timestampField) ||
if (("string" !== typeof field &&
"number" !== typeof field) ||

// dayjs surprisingly thinks `undefined` is a valid date:
// https://day.js.org/docs/en/parse/now#docsNav
"undefined" === typeof timestampField
"undefined" === typeof field
) {
// `INVALID_TIMESTAMP_VALUE` is a valid dayjs date. Another potential option is
// `dayjs(null)` to show "Invalid Date" in the UI.
timestampField = INVALID_TIMESTAMP_VALUE;
field = INVALID_TIMESTAMP_VALUE;
}

let dayjsTimestamp: Dayjs = dayjs.utc(timestampField);
let dayjsTimestamp: Dayjs = dayjs.utc(field);

// Sanitize invalid (e.g., "deadbeef") timestamps to `INVALID_TIMESTAMP_VALUE`; otherwise
// they'll show up in UI as "Invalid Date".
Expand All @@ -96,4 +90,3 @@ export {
convertToLogLevelValue,
isJsonObject,
};
export type {JsonLogEvent};
2 changes: 1 addition & 1 deletion new-log-viewer/src/services/formatters/LogbackFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
FormatterOptionsType,
} from "../../typings/formatters";
import {JsonObject} from "../../typings/js";
import {JsonLogEvent} from "../decoders/JsonlDecoder/utils";
import {JsonLogEvent} from "../../typings/logs";


/**
Expand Down
3 changes: 1 addition & 2 deletions new-log-viewer/src/typings/formatters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {JsonLogEvent} from "../services/decoders/JsonlDecoder/utils";

import {JsonLogEvent} from "./logs";

Check failure on line 1 in new-log-viewer/src/typings/formatters.ts

View workflow job for this annotation

GitHub Actions / lint-check

Expected 2 empty lines after import statement not followed by another import

/**
* Options for the LogbackFormatter.
Expand Down
15 changes: 14 additions & 1 deletion new-log-viewer/src/typings/logs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import {Nullable} from "./common";

Check warning on line 1 in new-log-viewer/src/typings/logs.ts

View workflow job for this annotation

GitHub Actions / lint-check

Run autofix to sort these imports!

import {Dayjs} from "dayjs";

import {

Check failure on line 5 in new-log-viewer/src/typings/logs.ts

View workflow job for this annotation

GitHub Actions / lint-check

Imports must not be broken into multiple lines if there are 1 or less elements

Check failure on line 5 in new-log-viewer/src/typings/logs.ts

View workflow job for this annotation

GitHub Actions / lint-check

Unexpected line break after this opening brace
JsonObject,
} from "./js";

Check failure on line 7 in new-log-viewer/src/typings/logs.ts

View workflow job for this annotation

GitHub Actions / lint-check

Unexpected line break before this closing brace

Check failure on line 7 in new-log-viewer/src/typings/logs.ts

View workflow job for this annotation

GitHub Actions / lint-check

Expected 2 empty lines after import statement not followed by another import

enum LOG_LEVEL {
NONE = 0,
Expand All @@ -15,7 +20,15 @@ type LogLevelFilter = Nullable<LOG_LEVEL[]>;

const INVALID_TIMESTAMP_VALUE = 0;

export type {LogLevelFilter};
interface JsonLogEvent {
timestamp: Dayjs,
level: LOG_LEVEL,
fields: JsonObject
}

export type {
JsonLogEvent,
LogLevelFilter};

Check failure on line 31 in new-log-viewer/src/typings/logs.ts

View workflow job for this annotation

GitHub Actions / lint-check

Expected a line break before this closing brace
export {
INVALID_TIMESTAMP_VALUE,
LOG_LEVEL,
Expand Down

0 comments on commit 114af1d

Please sign in to comment.