Skip to content

Commit 463fc53

Browse files
committed
feat: add configurable logging infrastructure
1 parent 056f784 commit 463fc53

File tree

12 files changed

+162
-12
lines changed

12 files changed

+162
-12
lines changed

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,21 @@ Open browser at http://localhost:3000
4949
```sh
5050
yarn test
5151
```
52+
53+
## Logging
54+
55+
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:
56+
57+
```
58+
localStorage.setItem('debug', '*'); localStorage.setItem('debug-level', 'debug');
59+
```
60+
61+
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:
62+
63+
- `main`: logs general application messages
64+
- `action`: logs all actions that modify the internal application state
65+
- `grpc`: logs all GRPC API requests and responses
66+
67+
Example filters: `main,action` will only log main and action messages. `*,-action` will log everything except action messages.
68+
69+
The value for `debug-level` determines the verbosity of the logs. The value can be one of `debug`, `info`, `warn`, or `error`.

app/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"proxy": "https://localhost:8443",
1616
"dependencies": {
1717
"@improbable-eng/grpc-web": "0.12.0",
18+
"debug": "4.1.1",
1819
"i18next": "19.4.1",
1920
"i18next-browser-languagedetector": "4.0.2",
2021
"mobx": "5.15.4",
@@ -28,6 +29,7 @@
2829
"@testing-library/jest-dom": "^4.2.4",
2930
"@testing-library/react": "^9.3.2",
3031
"@testing-library/user-event": "^7.1.2",
32+
"@types/debug": "4.1.5",
3133
"@types/google-protobuf": "3.7.2",
3234
"@types/jest": "^24.0.0",
3335
"@types/node": "^12.0.0",

app/src/action/channel.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { action } from 'mobx';
1+
import { action, toJS } from 'mobx';
2+
import { actionLog as log } from 'util/log';
23
import LndApi from 'api/lnd';
34
import { Store } from 'store';
45

@@ -19,6 +20,7 @@ class ChannelAction {
1920
* fetch channels from the LND RPC
2021
*/
2122
@action.bound async getChannels() {
23+
log.info('fetching channels');
2224
const channels = await this._lnd.listChannels();
2325
this._store.channels = channels.channelsList.map(c => ({
2426
chanId: c.chanId,
@@ -29,6 +31,7 @@ class ChannelAction {
2931
uptime: c.uptime,
3032
active: c.active,
3133
}));
34+
log.info('updated store.channels', toJS(this._store.channels));
3235
}
3336
}
3437

app/src/action/node.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { action } from 'mobx';
1+
import { action, toJS } from 'mobx';
2+
import { actionLog as log } from 'util/log';
23
import LndApi from 'api/lnd';
34
import { Store } from 'store';
45

@@ -19,7 +20,9 @@ class NodeAction {
1920
* fetch node info from the LND RPC
2021
*/
2122
@action.bound async getInfo() {
23+
log.info('fetching node information');
2224
this._store.info = await this._lnd.getInfo();
25+
log.info('updated store.info', toJS(this._store.info));
2326
}
2427
}
2528

app/src/action/swap.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { action } from 'mobx';
1+
import { action, toJS } from 'mobx';
22
import { SwapState, SwapType } from 'types/generated/loop_pb';
3+
import { actionLog as log } from 'util/log';
34
import LoopApi from 'api/loop';
45
import { Store } from 'store';
56

@@ -20,6 +21,7 @@ class SwapAction {
2021
* fetch swaps from the Loop RPC
2122
*/
2223
@action.bound async listSwaps() {
24+
log.info('fetching Loop history');
2325
const loopSwaps = await this._loop.listSwaps();
2426
this._store.swaps = loopSwaps.swapsList
2527
// sort the list with newest first as the API returns them out of order
@@ -31,6 +33,7 @@ class SwapAction {
3133
createdOn: new Date(s.initiationTime / 1000 / 1000),
3234
status: this._stateToString(s.state),
3335
}));
36+
log.info('updated store.swaps', toJS(this._store.swaps));
3437
}
3538

3639
/**

app/src/api/grpc.ts

+9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ProtobufMessage } from '@improbable-eng/grpc-web/dist/typings/message';
33
import { Metadata } from '@improbable-eng/grpc-web/dist/typings/metadata';
44
import { UnaryMethodDefinition } from '@improbable-eng/grpc-web/dist/typings/service';
55
import { DEV_HOST } from 'config';
6+
import { grpcLog as log } from 'util/log';
67

78
/**
89
* 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
1617
metadata?: Metadata.ConstructorArg,
1718
): Promise<TRes> => {
1819
return new Promise((resolve, reject) => {
20+
log.debug(
21+
`Request: ${methodDescriptor.service.serviceName}.${methodDescriptor.methodName}`,
22+
);
23+
log.debug(` - req: `, request.toObject());
1924
grpc.unary(methodDescriptor, {
2025
host: DEV_HOST,
2126
request,
2227
metadata,
2328
onEnd: ({ status, statusMessage, headers, message, trailers }) => {
29+
log.debug(' - status', status, statusMessage);
30+
log.debug(' - headers', headers);
2431
if (status === grpc.Code.OK && message) {
32+
log.debug(' - message', message.toObject());
2533
resolve(message as TRes);
2634
} else {
2735
reject(new Error(`${status}: ${statusMessage}`));
2836
}
37+
log.debug(' - trailers', trailers);
2938
},
3039
});
3140
});

app/src/api/lnd.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class LndApi {
1616
* call the LND `GetInfo` RPC and return the response
1717
*/
1818
async getInfo(): Promise<LND.GetInfoResponse.AsObject> {
19-
const req = new LND.GetInfoResponse();
19+
const req = new LND.GetInfoRequest();
2020
const res = await grpcRequest(Lightning.GetInfo, req, this._meta);
2121
return res.toObject();
2222
}

app/src/config.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,8 @@
55
// macaroon to use for LND auth
66
export const DEV_MACAROON = process.env.REACT_APP_DEV_MACAROON || '';
77

8+
// detect the host currently serving the app files
9+
const { protocol, hostname, port = '' } = window.location;
10+
const host = `${protocol}//${hostname}:${port}`;
811
// the GRPC server to make requests to
9-
export const DEV_HOST = process.env.REACT_APP_DEV_HOST || 'http://localhost:3000';
12+
export const DEV_HOST = process.env.REACT_APP_DEV_HOST || host;

app/src/i18n/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const config: InitOptions = {
4343
interpolation: {
4444
escapeValue: false,
4545
},
46+
detection: {
47+
lookupLocalStorage: 'lang',
48+
},
4649
};
4750

4851
i18n.use(LanguageDetector).use(initReactI18next).init(config);

app/src/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import ReactDOM from 'react-dom';
33
import 'mobx-react/batchingForReactDom';
44
import './i18n';
55
import './index.css';
6+
import { log } from 'util/log';
67
import App from './App';
78

9+
log.info('Rendering the App');
10+
811
ReactDOM.render(
912
<React.StrictMode>
1013
<App />

app/src/util/log.ts

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import debug, { Debugger } from 'debug';
2+
3+
enum LogLevel {
4+
debug = 1,
5+
info = 2,
6+
warn = 3,
7+
error = 4,
8+
none = 5,
9+
}
10+
11+
/**
12+
* A logger class with support for multiple namespaces and log levels.
13+
*/
14+
class Logger {
15+
private _levelToOutput: LogLevel;
16+
private _logger: Debugger;
17+
18+
constructor(levelToOutput: LogLevel, namespace: string) {
19+
this._levelToOutput = levelToOutput;
20+
this._logger = debug(namespace);
21+
}
22+
23+
/**
24+
* creates a new Logger instance by inspecting the executing environment
25+
*/
26+
static fromEnv(namespace: string): Logger {
27+
// by default, log everything in development and nothing in production
28+
let level = process.env.NODE_ENV !== 'production' ? LogLevel.debug : LogLevel.none;
29+
30+
if (localStorage.getItem('debug')) {
31+
// if a 'debug' key is found in localStorage, use the level in storage or 'debug' by default
32+
const storageLevel = localStorage.getItem('debug-level') || 'debug';
33+
level = LogLevel[storageLevel as keyof typeof LogLevel];
34+
} else if (process.env.NODE_ENV !== 'production') {
35+
// if running in development with no localStorage key, use debug
36+
level = LogLevel.debug;
37+
// set the keys so they can be easily changed in the browser DevTools
38+
localStorage.setItem('debug', '*');
39+
localStorage.setItem('debug-level', 'debug');
40+
}
41+
42+
return new Logger(level, namespace);
43+
}
44+
45+
/**
46+
* log a debug message
47+
*/
48+
debug = (message: string, ...args: any[]) => this._log(LogLevel.debug, message, args);
49+
50+
/**
51+
* log an info message
52+
*/
53+
info = (message: string, ...args: any[]) => this._log(LogLevel.info, message, args);
54+
55+
/**
56+
* log a warn message
57+
*/
58+
warn = (message: string, ...args: any[]) => this._log(LogLevel.warn, message, args);
59+
60+
/**
61+
* log an error message
62+
*/
63+
error = (message: string, ...args: any[]) => this._log(LogLevel.error, message, args);
64+
65+
/**
66+
* A shared logging function which will only output logs based on the level of this Logger instance
67+
* @param level the level of the message being logged
68+
* @param message the message to log
69+
* @param args optional additional arguments to log
70+
*/
71+
private _log(level: LogLevel, message: string, args: any[]) {
72+
// don't log if the level to output is greater than the level of this message
73+
if (this._levelToOutput > level) return;
74+
75+
// convert the provided log level number to the string name
76+
const prefix = Object.keys(LogLevel).reduce(
77+
(prev, curr) => (level === LogLevel[curr as keyof typeof LogLevel] ? curr : prev),
78+
'??',
79+
);
80+
81+
this._logger(`[${prefix}] ${message}`, ...args);
82+
}
83+
}
84+
85+
/**
86+
* the main logger for the app
87+
*/
88+
export const log = Logger.fromEnv('main');
89+
90+
/**
91+
* the logger for GRPC requests and responses
92+
*/
93+
export const grpcLog = Logger.fromEnv('grpc');
94+
95+
/**
96+
* the logger for state updates via mobx actions
97+
*/
98+
export const actionLog = Logger.fromEnv('action');

app/yarn.lock

+12-7
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,11 @@
13551355
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
13561356
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
13571357

1358+
1359+
version "4.1.5"
1360+
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
1361+
integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
1362+
13581363
"@types/eslint-visitor-keys@^1.0.0":
13591364
version "1.0.0"
13601365
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@@ -3463,20 +3468,20 @@ [email protected], debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
34633468
dependencies:
34643469
ms "2.0.0"
34653470

3471+
[email protected], debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
3472+
version "4.1.1"
3473+
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
3474+
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
3475+
dependencies:
3476+
ms "^2.1.1"
3477+
34663478
debug@^3.0.0, debug@^3.1.1, debug@^3.2.5:
34673479
version "3.2.6"
34683480
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
34693481
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
34703482
dependencies:
34713483
ms "^2.1.1"
34723484

3473-
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
3474-
version "4.1.1"
3475-
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
3476-
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
3477-
dependencies:
3478-
ms "^2.1.1"
3479-
34803485
decamelize@^1.2.0:
34813486
version "1.2.0"
34823487
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"

0 commit comments

Comments
 (0)