mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
feat: add configurable logging infrastructure
This commit is contained in:
parent
056f784689
commit
ee760b4b1e
12 changed files with 169 additions and 12 deletions
18
README.md
18
README.md
|
|
@ -49,3 +49,21 @@ Open browser at http://localhost:3000
|
|||
```sh
|
||||
yarn test
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
Client-side logs are disabled by default in production builds and enabled by default in a development environment. In production, logging can be turned on by adding a couple keys to your browser's `localStorage`. Simply run these two JS statements in you browser's DevTools console:
|
||||
|
||||
```
|
||||
localStorage.setItem('debug', '*'); localStorage.setItem('debug-level', 'debug');
|
||||
```
|
||||
|
||||
The value for `debug` is a namespace filter which determines which portions of the app to display logs for. The namespaces currently used by the app are as follows:
|
||||
|
||||
- `main`: logs general application messages
|
||||
- `action`: logs all actions that modify the internal application state
|
||||
- `grpc`: logs all GRPC API requests and responses
|
||||
|
||||
Example filters: `main,action` will only log main and action messages. `*,-action` will log everything except action messages.
|
||||
|
||||
The value for `debug-level` determines the verbosity of the logs. The value can be one of `debug`, `info`, `warn`, or `error`.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"proxy": "https://localhost:8443",
|
||||
"dependencies": {
|
||||
"@improbable-eng/grpc-web": "0.12.0",
|
||||
"debug": "4.1.1",
|
||||
"i18next": "19.4.1",
|
||||
"i18next-browser-languagedetector": "4.0.2",
|
||||
"mobx": "5.15.4",
|
||||
|
|
@ -28,6 +29,7 @@
|
|||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/debug": "4.1.5",
|
||||
"@types/google-protobuf": "3.7.2",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/node": "^12.0.0",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { action } from 'mobx';
|
||||
import { action, toJS } from 'mobx';
|
||||
import { actionLog as log } from 'util/log';
|
||||
import LndApi from 'api/lnd';
|
||||
import { Store } from 'store';
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ class ChannelAction {
|
|||
* fetch channels from the LND RPC
|
||||
*/
|
||||
@action.bound async getChannels() {
|
||||
log.info('fetching channels');
|
||||
const channels = await this._lnd.listChannels();
|
||||
this._store.channels = channels.channelsList.map(c => ({
|
||||
chanId: c.chanId,
|
||||
|
|
@ -29,6 +31,7 @@ class ChannelAction {
|
|||
uptime: c.uptime,
|
||||
active: c.active,
|
||||
}));
|
||||
log.info('updated store.channels', toJS(this._store.channels));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { action } from 'mobx';
|
||||
import { action, toJS } from 'mobx';
|
||||
import { actionLog as log } from 'util/log';
|
||||
import LndApi from 'api/lnd';
|
||||
import { Store } from 'store';
|
||||
|
||||
|
|
@ -19,7 +20,9 @@ class NodeAction {
|
|||
* fetch node info from the LND RPC
|
||||
*/
|
||||
@action.bound async getInfo() {
|
||||
log.info('fetching node information');
|
||||
this._store.info = await this._lnd.getInfo();
|
||||
log.info('updated store.info', toJS(this._store.info));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { action } from 'mobx';
|
||||
import { action, toJS } from 'mobx';
|
||||
import { SwapState, SwapType } from 'types/generated/loop_pb';
|
||||
import { actionLog as log } from 'util/log';
|
||||
import LoopApi from 'api/loop';
|
||||
import { Store } from 'store';
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ class SwapAction {
|
|||
* fetch swaps from the Loop RPC
|
||||
*/
|
||||
@action.bound async listSwaps() {
|
||||
log.info('fetching Loop history');
|
||||
const loopSwaps = await this._loop.listSwaps();
|
||||
this._store.swaps = loopSwaps.swapsList
|
||||
// sort the list with newest first as the API returns them out of order
|
||||
|
|
@ -31,6 +33,7 @@ class SwapAction {
|
|||
createdOn: new Date(s.initiationTime / 1000 / 1000),
|
||||
status: this._stateToString(s.state),
|
||||
}));
|
||||
log.info('updated store.swaps', toJS(this._store.swaps));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { ProtobufMessage } from '@improbable-eng/grpc-web/dist/typings/message';
|
|||
import { Metadata } from '@improbable-eng/grpc-web/dist/typings/metadata';
|
||||
import { UnaryMethodDefinition } from '@improbable-eng/grpc-web/dist/typings/service';
|
||||
import { DEV_HOST } from 'config';
|
||||
import { grpcLog as log } from 'util/log';
|
||||
|
||||
/**
|
||||
* Executes a single GRPC request and returns a promise which will resolve with the response
|
||||
|
|
@ -16,16 +17,24 @@ export const grpcRequest = <TReq extends ProtobufMessage, TRes extends ProtobufM
|
|||
metadata?: Metadata.ConstructorArg,
|
||||
): Promise<TRes> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
log.debug(
|
||||
`Request: ${methodDescriptor.service.serviceName}.${methodDescriptor.methodName}`,
|
||||
);
|
||||
log.debug(` - req: `, request.toObject());
|
||||
grpc.unary(methodDescriptor, {
|
||||
host: DEV_HOST,
|
||||
request,
|
||||
metadata,
|
||||
onEnd: ({ status, statusMessage, headers, message, trailers }) => {
|
||||
log.debug(' - status', status, statusMessage);
|
||||
log.debug(' - headers', headers);
|
||||
if (status === grpc.Code.OK && message) {
|
||||
log.debug(' - message', message.toObject());
|
||||
resolve(message as TRes);
|
||||
} else {
|
||||
reject(new Error(`${status}: ${statusMessage}`));
|
||||
}
|
||||
log.debug(' - trailers', trailers);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class LndApi {
|
|||
* call the LND `GetInfo` RPC and return the response
|
||||
*/
|
||||
async getInfo(): Promise<LND.GetInfoResponse.AsObject> {
|
||||
const req = new LND.GetInfoResponse();
|
||||
const req = new LND.GetInfoRequest();
|
||||
const res = await grpcRequest(Lightning.GetInfo, req, this._meta);
|
||||
return res.toObject();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
// flag to check if the app is running in a local development environment
|
||||
export const IS_DEV = process.env.NODE_ENV === 'development';
|
||||
|
||||
// flag to check if the app is running in a a production environment
|
||||
export const IS_PROD = process.env.NODE_ENV === 'production';
|
||||
|
||||
//
|
||||
// temporary placeholder values. these will be supplied via the UI in the future
|
||||
//
|
||||
|
|
@ -5,5 +11,8 @@
|
|||
// macaroon to use for LND auth
|
||||
export const DEV_MACAROON = process.env.REACT_APP_DEV_MACAROON || '';
|
||||
|
||||
// detect the host currently serving the app files
|
||||
const { protocol, hostname, port = '' } = window.location;
|
||||
const host = `${protocol}//${hostname}:${port}`;
|
||||
// the GRPC server to make requests to
|
||||
export const DEV_HOST = process.env.REACT_APP_DEV_HOST || 'http://localhost:3000';
|
||||
export const DEV_HOST = process.env.REACT_APP_DEV_HOST || host;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ const config: InitOptions = {
|
|||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
detection: {
|
||||
lookupLocalStorage: 'lang',
|
||||
},
|
||||
};
|
||||
|
||||
i18n.use(LanguageDetector).use(initReactI18next).init(config);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ import ReactDOM from 'react-dom';
|
|||
import 'mobx-react/batchingForReactDom';
|
||||
import './i18n';
|
||||
import './index.css';
|
||||
import { log } from 'util/log';
|
||||
import App from './App';
|
||||
|
||||
log.info('Rendering the App');
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
|
|
|
|||
99
app/src/util/log.ts
Normal file
99
app/src/util/log.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { IS_DEV } from 'config';
|
||||
import debug, { Debugger } from 'debug';
|
||||
|
||||
enum LogLevel {
|
||||
debug = 1,
|
||||
info = 2,
|
||||
warn = 3,
|
||||
error = 4,
|
||||
none = 5,
|
||||
}
|
||||
|
||||
/**
|
||||
* A logger class with support for multiple namespaces and log levels.
|
||||
*/
|
||||
class Logger {
|
||||
private _levelToOutput: LogLevel;
|
||||
private _logger: Debugger;
|
||||
|
||||
constructor(levelToOutput: LogLevel, namespace: string) {
|
||||
this._levelToOutput = levelToOutput;
|
||||
this._logger = debug(namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a new Logger instance by inspecting the executing environment
|
||||
*/
|
||||
static fromEnv(namespace: string): Logger {
|
||||
// by default, log everything in development and nothing in production
|
||||
let level = IS_DEV ? LogLevel.debug : LogLevel.none;
|
||||
|
||||
if (localStorage.getItem('debug')) {
|
||||
// if a 'debug' key is found in localStorage, use the level in storage or 'debug' by default
|
||||
const storageLevel = localStorage.getItem('debug-level') || 'debug';
|
||||
level = LogLevel[storageLevel as keyof typeof LogLevel];
|
||||
} else if (IS_DEV) {
|
||||
// if running in development with no localStorage key, use debug
|
||||
level = LogLevel.debug;
|
||||
// set the keys so they can be easily changed in the browser DevTools
|
||||
localStorage.setItem('debug', '*');
|
||||
localStorage.setItem('debug-level', 'debug');
|
||||
}
|
||||
|
||||
return new Logger(level, namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* log a debug message
|
||||
*/
|
||||
debug = (message: string, ...args: any[]) => this._log(LogLevel.debug, message, args);
|
||||
|
||||
/**
|
||||
* log an info message
|
||||
*/
|
||||
info = (message: string, ...args: any[]) => this._log(LogLevel.info, message, args);
|
||||
|
||||
/**
|
||||
* log a warn message
|
||||
*/
|
||||
warn = (message: string, ...args: any[]) => this._log(LogLevel.warn, message, args);
|
||||
|
||||
/**
|
||||
* log an error message
|
||||
*/
|
||||
error = (message: string, ...args: any[]) => this._log(LogLevel.error, message, args);
|
||||
|
||||
/**
|
||||
* A shared logging function which will only output logs based on the level of this Logger instance
|
||||
* @param level the level of the message being logged
|
||||
* @param message the message to log
|
||||
* @param args optional additional arguments to log
|
||||
*/
|
||||
private _log(level: LogLevel, message: string, args: any[]) {
|
||||
// don't log if the level to output is greater than the level of this message
|
||||
if (this._levelToOutput > level) return;
|
||||
|
||||
// convert the provided log level number to the string name
|
||||
const prefix = Object.keys(LogLevel).reduce(
|
||||
(prev, curr) => (level === LogLevel[curr as keyof typeof LogLevel] ? curr : prev),
|
||||
'??',
|
||||
);
|
||||
|
||||
this._logger(`[${prefix}] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the main logger for the app
|
||||
*/
|
||||
export const log = Logger.fromEnv('main');
|
||||
|
||||
/**
|
||||
* the logger for GRPC requests and responses
|
||||
*/
|
||||
export const grpcLog = Logger.fromEnv('grpc');
|
||||
|
||||
/**
|
||||
* the logger for state updates via mobx actions
|
||||
*/
|
||||
export const actionLog = Logger.fromEnv('action');
|
||||
|
|
@ -1355,6 +1355,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
|
||||
|
||||
"@types/debug@4.1.5":
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
|
||||
integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
|
||||
|
||||
"@types/eslint-visitor-keys@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||
|
|
@ -3463,6 +3468,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
|
|||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4.1.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
||||
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^3.0.0, debug@^3.1.1, debug@^3.2.5:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
|
|
@ -3470,13 +3482,6 @@ debug@^3.0.0, debug@^3.1.1, debug@^3.2.5:
|
|||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
||||
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decamelize@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue