> We don't need no - data structures. Pam pam pam pam. > No custom implementations - in the classroom. > Engeneers don't - reinvent the weel
npm install @tty-pt/logsThis is what came out of me wanting to find a solution.
It combines an "interval tree" and a normal binary search tree into one data structure that fits our needs. While meanwhile making the everyday's developer job as easy as "I'm interested in logs like these".
You can fetch logs any way you want, stream, do it over intervals.. Whatever.
But you won't have to do it for every card. Just do it once in a central place.
And every place needing them can request this API.
It's smart enough to know how to handle it.
``es6
// firstly, we inherit the base class:
export default class MyLogs extends Logs {
static CONSTANTS = {
DEFAULT_LEVELS,
DEFAULT_SERVICE,
};
constructor() {
super({
url: 'v1/logs/',
transform,
stream: true,
streamUrl: "/ws/logs",
streamTransform,
fields: {
level: { type: "enumeration", default: DEFAULT_LEVELS },
service: { type: "enumeration", default: DEFAULT_SERVICE },
tags: { type: "tags" },
message: { type: "string" },
robot: { type: "enumeration" },
},
});
}
}
// we also implement a simple hook to use the class more easily in react:
export function useLogs(myQuery, dependencies) {
const logs = new MyLogs();
const getFiltered = useCallback(
({ limit = 0, ...query }) => {
const filtered = logs.filter(query);
return limit ? filtered.slice(0, limit) : filtered;
},
[],
);
const [logsData, setLogsData] = useState(getFiltered(myQuery));
useEffect(
() => {
setLogsData(getFiltered(myQuery));
return logs.subscribe(() => setLogsData(getFiltered(myQuery)), myQuery);
},
dependencies,
);
return logsData;
}
// then, in a component, we do:
const filteredLogs = useLogs(
{
level: levels,
service,
tags,
message,
robot: robots,
fromDate: selectedFromDate,
toDate: selectedToDate,
},
[levels, service, tags, message, robots, selectedFromDate, selectedToDate],
);
// or even:
const { robot } = props;
const logsData = useLogs(
{
robot: {
[robot.name]: true,
},
tags: {
ui: true,
},
level: {
"INFO": true,
}
limit: 2,
},
[robot.name],
);
``
And that's all you need.
default: async (url) => (await fetch(url)).json()
default: log => log
signature:
- log: the item to transform
- index: the index of the item in the received data
- array: the full array of data to be added
- isStream: a boolean that identifies if it came from the stream
default: false
default: streamUrl => new WebSocket(streamUrl)
default: log => log
signature:
- log: the item to transform
#### type
A string that identifies the type of field (see "types" below).
#### default
The default value for the filter of the type
default: 3000
default: 20000
default: timestamp
#### default
The default filter value
#### unparse
A function that says how to turn a filter into an URL parameter
signature:
- label: the label of the url parameter
- value: the value of the filters being queried
#### filter
How to filter over a field of this type
signature:
- field: the value of the specific field in a log
- filter: the value of the specific filter
- item: the whole log item
- In an item: { message: "hello world" }
- In a query: { message: "hello" }
- As a parameter string: "message=hello"
default: ""
- In an item: { timestamp: 33, level: "INFO" }
- In a query: { level: { "INFO": true, "WARNING": true } }
- As a parameter string: "level=INFO,WARNING"
default: {}
- In item: { timestamp: 33, ui: "hello" }
- In a query: { tags: { "ui": true, "iu": true } }
- As a parameter string: "ui=True,iu=True"
default: {}
- In item: { timestamp: 33 }
- In a query: { fromDate: new Date() }
- As a parameter string: "fromDate=1736357786000"
default: null
- In item: { timestamp: 33 }
- In a query: { toDate: new Date() }
- As a parameter string: "toDate=1736357786000"
default: null
- In item: (doesn't apply)
- In a query: { limit: 2 }
- As a parameter string: "limit=2"
default: 20000