Skip to content

Commit

Permalink
feat: add filter and find methods to logger
Browse files Browse the repository at this point in the history
  • Loading branch information
eysi09 committed Apr 26, 2018
1 parent 7ce80de commit 814733b
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 122 deletions.
15 changes: 12 additions & 3 deletions src/logger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import chalk from "chalk"
import { combine } from "./renderers"
import {
duration,
getChildNodes,
findLogEntry,
getChildEntries,
mergeLogOpts,
} from "./util"
import {
Expand Down Expand Up @@ -122,6 +123,14 @@ export abstract class LogNode {
return this.addNode(LogLevel.error, { ...makeLogOpts(entryVal), entryStyle: EntryStyle.error })
}

public findById(id: string): LogEntry | void {
return findLogEntry(this, entry => entry.opts.id === id)
}

public filterBySection(section: string): LogEntry[] {
return getChildEntries(this).filter(entry => entry.opts.section === section)
}

}

export class LogEntry extends LogNode {
Expand Down Expand Up @@ -173,7 +182,7 @@ export class LogEntry extends LogNode {
// Update node and child nodes
private deepSetState(opts: LogEntryOpts, status: EntryStatus): void {
this.setOwnState(opts, status)
getChildNodes(this).forEach(entry => {
getChildEntries(this).forEach(entry => {
if (entry.status === EntryStatus.ACTIVE) {
entry.setOwnState({}, EntryStatus.DONE)
}
Expand Down Expand Up @@ -248,7 +257,7 @@ export class RootLogNode extends LogNode {
}

public getLogEntries(): LogEntry[] {
return getChildNodes(<any>this).filter(entry => !entry.notOriginatedFromLogger())
return getChildEntries(this).filter(entry => !entry.notOriginatedFromLogger())
}

public header(
Expand Down
1 change: 1 addition & 0 deletions src/logger/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,5 @@ export interface LogEntryOpts {
notOriginatedFromLogger?: boolean
showDuration?: boolean
error?: GardenError | Error
id?: string
}
53 changes: 31 additions & 22 deletions src/logger/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,52 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { flatten } from "lodash"

import { LogEntryOpts } from "./types"
import { LogEntry, LogNode } from "."

export interface Node {
children: any[]
}

export type LogOptsResolvers = { [K in keyof LogEntryOpts]?: Function }

// TODO Tail call optimization?
export function getNodeListFromTree<T extends Node>(node: T): T[] {
let arr: T[] = []
arr.push(node)
if (node.children.length === 0) {
return arr
export type ProcessNode<T extends Node = Node> = (node: T) => boolean

// Assumes root node can be of different type than child nodes
function traverseChildren<T extends Node, U extends Node>(node: T | U, cb: ProcessNode<U>) {
const children = node.children
for (let idx = 0; idx < children.length; idx++) {
const proceed = cb(children[idx])
if (!proceed) {
return
}
traverseChildren(children[idx], cb)
}
return arr.concat(flatten(node.children.map(child => getNodeListFromTree(child))))
}

export function getChildNodes<T extends Node>(node: T): T[] {
return getNodeListFromTree(node).slice(1)
export function getChildNodes<T extends Node, U extends Node = T>(node: T | U): U[] {
let array: U[] = []
traverseChildren<T, U>(node, child => {
array.push(child)
return true
})
return array
}

export function traverseTree<T extends Node>(root: T, visitNode: Function): void {
let stack: any[] = []
stack.push(root)
export function getChildEntries(node: LogNode): LogEntry[] {
return getChildNodes<LogNode, LogEntry>(node)
}

while (stack.length !== 0) {
const node = stack.pop()
visitNode(node)
if (node.children.length !== 0) {
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i])
}
export function findLogEntry(node: LogNode, predicate: ProcessNode<LogEntry>): LogEntry | void {
let found
traverseChildren<LogNode, LogEntry>(node, entry => {
if (predicate(entry)) {
found = entry
return false
}
}
return true
})
return found
}

function mergeWithResolvers(objA: any, objB: any, resolvers: any = {}) {
Expand Down
4 changes: 2 additions & 2 deletions src/logger/writers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import * as winston from "winston"
import chalk from "chalk"
const stripAnsi = require("strip-ansi")

import { getChildNodes, interceptStream } from "./util"
import { getChildEntries, interceptStream } from "./util"
import {
EntryStatus,
LogLevel,
Expand Down Expand Up @@ -253,7 +253,7 @@ export class FancyConsoleWriter extends Writer {
public render(rootLogNode: RootLogNode): string[] | null {
let hasActiveEntries = false
const level = this.level || rootLogNode.level
const entries = <any>getChildNodes(rootLogNode)
const entries = getChildEntries(rootLogNode)

/**
* This is a bit ugly for performance sake.
Expand Down
222 changes: 127 additions & 95 deletions test/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,50 @@
import { expect } from "chai"

import { LogLevel, EntryStatus, LogSymbolType, LoggerType } from "../../src/logger/types"
import { BasicConsoleWriter, FancyConsoleWriter } from "../../src/logger/writers"
import { BasicConsoleWriter, FancyConsoleWriter } from "../../src/logger/writers"
import { RootLogNode } from "../../src/logger"
import { getChildNodes } from "../../src/logger/util"

const logger = new RootLogNode({ level: LogLevel.silent })
describe("LogNode", () => {
describe("findById", () => {
it("should return the first log entry with a matching id and undefined otherwise", () => {
const logger = new RootLogNode({ level: LogLevel.info })
logger.info({msg: "0"})
logger.info({msg: "a1", id: "a"})
logger.info({msg: "a2", id: "a"})
expect(logger.findById("a")["opts"]["msg"]).to.eql("a1")
expect(logger.findById("z")).to.be.undefined
})
})

describe("filterBySection", () => {
it("should return an array of all entries with the matching section name", () => {
const logger = new RootLogNode({ level: LogLevel.info })
logger.info({section: "s0"})
logger.info({section: "s1", id: "a"})
logger.info({section: "s2"})
logger.info({section: "s1", id: "b"})
const s1 = logger.filterBySection("s1")
const sEmpty = logger.filterBySection("s99")
expect(s1.map(entry => entry.opts.id)).to.eql(["a", "b"])
expect(sEmpty).to.eql([])
})
})

logger.error("error")
logger.warn("warn")
logger.info("info")
logger.verbose("verbose")
logger.debug("debug")
logger.silly("silly")
})

describe("RootLogNode", () => {
const logger = new RootLogNode({ level: LogLevel.info })

logger.error("error")
logger.warn("warn")
logger.info("info")
logger.verbose("verbose")
logger.debug("debug")
logger.silly("silly")

describe("getLogEntries", () => {
it("should return an ordered list of log entries", () => {

const entries = logger.getLogEntries()
const levels = entries.map(e => e.level)

Expand Down Expand Up @@ -49,105 +75,111 @@ describe("RootLogNode", () => {
})
})

describe("BasicConsoleWriter.render", () => {
it("should return a string if log level is geq than entry level", () => {
const writer = new BasicConsoleWriter({ level: LogLevel.silent })
const logger2 = new RootLogNode({ level: LogLevel.silent })
const entry = logger2.info("")
const out1 = writer.render(entry, logger2)
writer.level = LogLevel.verbose
const out2 = writer.render(entry, logger2)

expect(out1).to.be.a("null")
expect(out2).to.be.a("string")
})
})

describe("BasicConsoleWriter.render", () => {
it("should return a string if log level is geq than entry level", () => {
const writer = new BasicConsoleWriter({ level: LogLevel.silent })
const logger = new RootLogNode({ level: LogLevel.silent })
const entry = logger.info("")
const out1 = writer.render(entry, logger)
writer.level = LogLevel.verbose
const out2 = writer.render(entry, logger)

expect(out1).to.be.a("null")
expect(out2).to.be.a("string")
})
})

describe("FancyConsoleWriter.render", () => {
it("should return an array of strings if log level is geq than respective entry level", () => {
const writer = new FancyConsoleWriter({ level: LogLevel.silent })
const logger3 = new RootLogNode({level: LogLevel.silent})
const entry = logger3.info("")
const out1 = writer.render(logger3)
writer.level = LogLevel.verbose
const out2 = writer.render(logger3)
describe("FancyConsoleWriter.render", () => {
it("should return an array of strings if log level is geq than respective entry level", () => {
const writer = new FancyConsoleWriter({ level: LogLevel.silent })
const logger = new RootLogNode({ level: LogLevel.silent })
const entry = logger.info("")
const out1 = writer.render(logger)
writer.level = LogLevel.verbose
const out2 = writer.render(logger)

writer.stop()
writer.stop()

expect(out1).to.be.a("null")
expect(out2).to.be.an("array").of.length(1)
})
expect(out1).to.be.a("null")
expect(out2).to.be.an("array").of.length(1)
})
})

describe("LogEntry", () => {
const entry = logger.children[0]
describe("setState", () => {
it("should update entry state and optionally append new msg to previous msg", () => {
entry.setState("new")
expect(entry["opts"]["msg"]).to.equal("new")
entry.setState({ msg: "new2", append: true })
expect(entry["opts"]["msg"]).to.eql(["new", "new2"])
})
describe("LogEntry", () => {
const logger = new RootLogNode({ level: LogLevel.info })
const entry = logger.info("")
describe("setState", () => {
it("should update entry state and optionally append new msg to previous msg", () => {
entry.setState("new")
expect(entry["opts"]["msg"]).to.equal("new")
entry.setState({ msg: "new2", append: true })
expect(entry["opts"]["msg"]).to.eql(["new", "new2"])
})
describe("setDone", () => {
it("should update entry state and set status to done", () => {
entry.setDone()
expect(entry["status"]).to.equal(EntryStatus.DONE)
})
})
describe("setDone", () => {
it("should update entry state and set status to done", () => {
entry.setDone()
expect(entry["status"]).to.equal(EntryStatus.DONE)
})
describe("setSuccess", () => {
it("should update entry state and set status and symbol to success", () => {
entry.setSuccess()
expect(entry["status"]).to.equal(EntryStatus.SUCCESS)
expect(entry["opts"]["symbol"]).to.equal(LogSymbolType.success)
})
})
describe("setSuccess", () => {
it("should update entry state and set status and symbol to success", () => {
entry.setSuccess()
expect(entry["status"]).to.equal(EntryStatus.SUCCESS)
expect(entry["opts"]["symbol"]).to.equal(LogSymbolType.success)
})
describe("setError", () => {
it("should update entry state and set status and symbol to error", () => {
entry.setError()
expect(entry["status"]).to.equal(EntryStatus.ERROR)
expect(entry["opts"]["symbol"]).to.equal(LogSymbolType.error)
})
})
describe("setError", () => {
it("should update entry state and set status and symbol to error", () => {
entry.setError()
expect(entry["status"]).to.equal(EntryStatus.ERROR)
expect(entry["opts"]["symbol"]).to.equal(LogSymbolType.error)
})
describe("setWarn", () => {
it("should update entry state and set status and symbol to warn", () => {
entry.setWarn()
expect(entry["status"]).to.equal(EntryStatus.WARN)
expect(entry["opts"]["symbol"]).to.equal(LogSymbolType.warn)
})
})
describe("setWarn", () => {
it("should update entry state and set status and symbol to warn", () => {
entry.setWarn()
expect(entry["status"]).to.equal(EntryStatus.WARN)
expect(entry["opts"]["symbol"]).to.equal(LogSymbolType.warn)
})
})
})

describe("util", () => {
describe("getChildNodes", () => {
it("should convert an n-ary tree into an ordered list of nodes", () => {
const graph = {
children: [
{
children: [
{
children: [
{ children: [], id: 3 },
],
id: 2,
},
{ children: [], id: 4 },
{ children: [], id: 5 },
],
id: 1,
},
{
children: [

],
id: 6,
},
],
id: "root",
}
const nodeList = getChildNodes(graph)
expect(nodeList.map(n => n.id)).to.eql([1, 2, 3, 4, 5, 6])
})
describe("util", () => {
describe("getChildNodes", () => {
it("should convert an n-ary tree into an ordered list of child nodes (skipping the root)", () => {
interface TestNode {
children: any[]
id: number
}
const graph = {
children: [
{
children: [
{
children: [
{ children: [], id: 3 },
],
id: 2,
},
{ children: [], id: 4 },
{ children: [], id: 5 },
],
id: 1,
},
{
children: [

],
id: 6,
},
],
id: 0,
}
const nodeList = getChildNodes<TestNode>(graph)
expect(nodeList.map(n => n.id)).to.eql([1, 2, 3, 4, 5, 6])
})
})
})

0 comments on commit 814733b

Please sign in to comment.