1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00

feat: rework loggers and pass data to the UI

This commit is contained in:
Ash Keel 2022-11-24 01:54:56 +01:00
parent 0a3b6eae4c
commit bb8f3f754e
No known key found for this signature in database
GPG key ID: BAD8D93E7314ED3E
16 changed files with 310 additions and 78 deletions

3
.gitignore vendored
View file

@ -6,4 +6,5 @@
.vscode .vscode
.idea .idea
strimertul_* strimertul_*
build/bin build/bin
*.log

27
app.go
View file

@ -4,14 +4,15 @@ import (
"context" "context"
"strconv" "strconv"
"github.com/nicklaw5/helix/v2"
"github.com/strimertul/strimertul/modules" "github.com/strimertul/strimertul/modules"
"github.com/strimertul/strimertul/modules/database" "github.com/strimertul/strimertul/modules/database"
"github.com/strimertul/strimertul/modules/http" "github.com/strimertul/strimertul/modules/http"
"github.com/strimertul/strimertul/modules/twitch" "github.com/strimertul/strimertul/modules/twitch"
"git.sr.ht/~hamcha/containers"
"github.com/nicklaw5/helix/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -21,12 +22,14 @@ type App struct {
cliParams *cli.Context cliParams *cli.Context
driver DatabaseDriver driver DatabaseDriver
manager *modules.Manager manager *modules.Manager
ready *containers.RWSync[bool]
} }
// NewApp creates a new App application struct // NewApp creates a new App application struct
func NewApp(cliParams *cli.Context) *App { func NewApp(cliParams *cli.Context) *App {
return &App{ return &App{
cliParams: cliParams, cliParams: cliParams,
ready: containers.NewRWSync(false),
} }
} }
@ -84,8 +87,19 @@ func (a *App) startup(ctx context.Context) {
} }
}() }()
a.ready.Set(true)
runtime.EventsEmit(ctx, "ready", true)
logger.Info("app is ready")
// Start redirecting logs to UI
go func() {
for entry := range incomingLogs {
runtime.EventsEmit(ctx, "log-event", entry)
}
}()
// Run HTTP server // Run HTTP server
go failOnError(httpServer.Listen(), "HTTP server stopped") failOnError(httpServer.Listen(), "HTTP server stopped")
} }
func (a *App) stop(context.Context) { func (a *App) stop(context.Context) {
@ -101,6 +115,9 @@ func (a *App) AuthenticateKVClient(id string) {
} }
func (a *App) IsServerReady() bool { func (a *App) IsServerReady() bool {
if !a.ready.Get() {
return false
}
return a.manager.Modules[modules.ModuleHTTP].Status().Working return a.manager.Modules[modules.ModuleHTTP].Status().Working
} }
@ -121,3 +138,7 @@ func (a *App) GetTwitchAuthURL() string {
func (a *App) GetTwitchLoggedUser() (helix.User, error) { func (a *App) GetTwitchLoggedUser() (helix.User, error) {
return a.manager.Modules[modules.ModuleTwitch].(*twitch.Client).GetLoggedUser() return a.manager.Modules[modules.ModuleTwitch].(*twitch.Client).GetLoggedUser()
} }
func (a *App) GetLastLogs() []LogEntry {
return lastLogs
}

View file

@ -36,7 +36,7 @@
"bind": "Webserver Bind", "bind": "Webserver Bind",
"kilovolt-password": "Kilovolt password", "kilovolt-password": "Kilovolt password",
"kilovolt-placeholder": "Leave empty to disable authentication", "kilovolt-placeholder": "Leave empty to disable authentication",
"bind-help": "Every application that uses strimertul will need to be updated, your browser will be redirected to the new URL automatically.", "bind-help": "Every application that uses {{APPNAME}} will need to be updated!",
"static-path": "Static assets (leave empty to disable)", "static-path": "Static assets (leave empty to disable)",
"static-placeholder": "Absolute path to static assets", "static-placeholder": "Absolute path to static assets",
"static-help": "Will be served at the following URL: {{url}}", "static-help": "Will be served at the following URL: {{url}}",
@ -82,7 +82,7 @@
}, },
"sim-events": "Send test event", "sim-events": "Send test event",
"auth-button": "Authenticate with Twitch", "auth-button": "Authenticate with Twitch",
"auth-message": "Click the following button to authenticate strimertul with your Twitch account:", "auth-message": "Click the following button to authenticate {{APPNAME}} with your Twitch account:",
"current-status": "Current status" "current-status": "Current status"
} }
}, },
@ -130,7 +130,7 @@
}, },
"auth": { "auth": {
"title": "Authentication required", "title": "Authentication required",
"desc": "The installation of strimertül you are trying to reach is protected with a password. Please write the password below to access the control panel.", "desc": "The installation of {{APPNAME}} you are trying to reach is protected with a password. Please write the password below to access the control panel.",
"no-pwd-note": " If the database has no password (for example, it was recently changed from having one to none), leave the field empty.", "no-pwd-note": " If the database has no password (for example, it was recently changed from having one to none), leave the field empty.",
"password": "Password", "password": "Password",
"submit": "Authenticate" "submit": "Authenticate"
@ -284,6 +284,7 @@
"wip": { "wip": {
"header": "WIP - Page under development", "header": "WIP - Page under development",
"text": "This page is still under construction, apologies for the lackluster view :(" "text": "This page is still under construction, apologies for the lackluster view :("
} },
"loading": "{{APPNAME}} is starting up, please wait!"
} }
} }

View file

@ -2,10 +2,12 @@ import { configureStore } from '@reduxjs/toolkit';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import thunkMiddleware from 'redux-thunk'; import thunkMiddleware from 'redux-thunk';
import apiReducer from './api/reducer'; import apiReducer from './api/reducer';
import loggingReducer from './logging/reducer';
const store = configureStore({ const store = configureStore({
reducer: { reducer: {
api: apiReducer.reducer, api: apiReducer.reducer,
logging: loggingReducer.reducer,
}, },
middleware: (getDefaultMiddleware) => middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ getDefaultMiddleware({

View file

@ -0,0 +1,53 @@
/* eslint-disable no-param-reassign */
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { main } from '@wailsapp/go/models';
interface ProcessedLogEntry {
time: Date;
caller: string;
level: string;
message: string;
data: object;
}
function processEntry({
time,
caller,
level,
message,
data,
}: main.LogEntry): ProcessedLogEntry {
return {
time: new Date(time),
caller,
level,
message,
data: JSON.parse(data) as object,
};
}
interface LoggingState {
messages: ProcessedLogEntry[];
}
const initialState: LoggingState = {
messages: [],
};
const loggingReducer = createSlice({
name: 'logging',
initialState,
reducers: {
loadedLogData(state, { payload }: PayloadAction<main.LogEntry[]>) {
state.messages = payload.map(processEntry);
},
receivedEvent(state, { payload }: PayloadAction<main.LogEntry>) {
state.messages = [...state.messages, processEntry(payload)];
},
clearedEvents(state) {
state.messages = [];
},
},
});
export default loggingReducer;

View file

@ -1,5 +1,3 @@
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { import {
ChatBubbleIcon, ChatBubbleIcon,
DashboardIcon, DashboardIcon,
@ -10,34 +8,45 @@ import {
TableIcon, TableIcon,
TimerIcon, TimerIcon,
} from '@radix-ui/react-icons'; } from '@radix-ui/react-icons';
import { EventsOff, EventsOn } from '@wailsapp/runtime/runtime';
import { t } from 'i18next';
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { Route, Routes } from 'react-router-dom'; import { Route, Routes } from 'react-router-dom';
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from 'react-toastify';
import { GetKilovoltBind } from '@wailsapp/go/main/App';
import { delay } from 'src/lib/time-utils'; import {
import Dashboard from './pages/Dashboard'; GetKilovoltBind,
import Sidebar, { RouteSection } from './components/Sidebar'; GetLastLogs,
import ServerSettingsPage from './pages/ServerSettings'; IsServerReady,
} from '@wailsapp/go/main/App';
import { main } from '@wailsapp/go/models';
import { RootState, useAppDispatch } from '../store'; import { RootState, useAppDispatch } from '../store';
import { createWSClient, useAuthBypass } from '../store/api/reducer'; import { createWSClient, useAuthBypass } from '../store/api/reducer';
import { ConnectionStatus } from '../store/api/types'; import { ConnectionStatus } from '../store/api/types';
import { styled } from './theme'; import loggingReducer from '../store/logging/reducer';
import Sidebar, { RouteSection } from './components/Sidebar';
import AuthDialog from './pages/AuthDialog';
import TwitchBotCommandsPage from './pages/BotCommands';
import TwitchBotTimersPage from './pages/BotTimers';
import ChatAlertsPage from './pages/ChatAlerts';
import Dashboard from './pages/Dashboard';
import DebugPage from './pages/Debug';
import LoyaltyConfigPage from './pages/LoyaltyConfig';
import LoyaltyQueuePage from './pages/LoyaltyQueue';
import LoyaltyRewardsPage from './pages/LoyaltyRewards';
import ServerSettingsPage from './pages/ServerSettings';
import StrimertulPage from './pages/Strimertul';
import TwitchSettingsPage from './pages/TwitchSettings';
import { APPNAME, styled } from './theme';
// @ts-expect-error Asset import // @ts-expect-error Asset import
import spinner from '../assets/icon-loading.svg'; import spinner from '../assets/icon-loading.svg';
import TwitchSettingsPage from './pages/TwitchSettings';
import TwitchBotCommandsPage from './pages/BotCommands';
import TwitchBotTimersPage from './pages/BotTimers';
import AuthDialog from './pages/AuthDialog';
import ChatAlertsPage from './pages/ChatAlerts';
import LoyaltyConfigPage from './pages/LoyaltyConfig';
import LoyaltyQueuePage from './pages/LoyaltyQueue';
import StrimertulPage from './pages/Strimertul';
import DebugPage from './pages/Debug';
import LoyaltyRewardsPage from './pages/LoyaltyRewards';
const LoadingDiv = styled('div', { const LoadingDiv = styled('div', {
display: 'flex', display: 'flex',
flexDirection: 'column',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
minHeight: '100vh', minHeight: '100vh',
@ -47,10 +56,15 @@ const Spinner = styled('img', {
maxWidth: '100px', maxWidth: '100px',
}); });
function Loading() { interface LoadingProps {
message: string;
}
function Loading({ message }: React.PropsWithChildren<LoadingProps>) {
return ( return (
<LoadingDiv> <LoadingDiv>
<Spinner src={spinner as string} alt="Loading..." /> <Spinner src={spinner as string} alt="Loading..." />
<p>{message}</p>
</LoadingDiv> </LoadingDiv>
); );
} }
@ -143,6 +157,7 @@ const PageWrapper = styled('div', {
}); });
export default function App(): JSX.Element { export default function App(): JSX.Element {
const [ready, setReady] = useState(false);
const client = useSelector((state: RootState) => state.api.client); const client = useSelector((state: RootState) => state.api.client);
const connected = useSelector( const connected = useSelector(
(state: RootState) => state.api.connectionStatus, (state: RootState) => state.api.connectionStatus,
@ -150,16 +165,7 @@ export default function App(): JSX.Element {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const connectToKV = async () => { const connectToKV = async () => {
let address = ''; const address = await GetKilovoltBind();
while (address === '') {
// eslint-disable-next-line no-await-in-loop
address = await GetKilovoltBind();
if (address === '') {
// Server not ready yet, wait a second
// eslint-disable-next-line no-await-in-loop
await delay(1000);
}
}
await dispatch( await dispatch(
createWSClient({ createWSClient({
address: `ws://${address}/ws`, address: `ws://${address}/ws`,
@ -167,17 +173,43 @@ export default function App(): JSX.Element {
); );
}; };
// Get application logs
useEffect(() => { useEffect(() => {
void GetLastLogs().then((logs) => {
dispatch(loggingReducer.actions.loadedLogData(logs));
});
EventsOn('log-event', (event: main.LogEntry) => {
dispatch(loggingReducer.actions.receivedEvent(event));
});
return () => {
EventsOff('log-event');
};
}, []);
useEffect(() => {
void IsServerReady().then(setReady);
EventsOn('ready', (newValue: boolean) => {
setReady(newValue);
});
return () => {
EventsOff('ready');
};
}, []);
useEffect(() => {
if (!ready) {
return;
}
if (!client) { if (!client) {
void connectToKV(); void connectToKV();
} }
if (connected === ConnectionStatus.AuthenticationNeeded) { if (connected === ConnectionStatus.AuthenticationNeeded) {
void dispatch(useAuthBypass()); void dispatch(useAuthBypass());
} }
}); }, [ready, connected]);
if (connected === ConnectionStatus.NotConnected) { if (connected === ConnectionStatus.NotConnected) {
return <Loading />; return <Loading message={t('special.loading', { APPNAME })} />;
} }
if (connected === ConnectionStatus.AuthenticationNeeded) { if (connected === ConnectionStatus.AuthenticationNeeded) {

View file

@ -1,7 +1,7 @@
import { styled } from '@stitches/react'; import { styled } from '@stitches/react';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, InputBox, TextBlock } from '../theme'; import { APPNAME, Button, InputBox, TextBlock } from '../theme';
const AuthWrapper = styled('div', { const AuthWrapper = styled('div', {
alignItems: 'center', alignItems: 'center',
@ -59,7 +59,9 @@ export default function AuthDialog(): React.ReactElement {
> >
<AuthTitle>{t('pages.auth.title')}</AuthTitle> <AuthTitle>{t('pages.auth.title')}</AuthTitle>
<Content> <Content>
<TextBlock spacing="none">{t('pages.auth.desc')}</TextBlock> <TextBlock spacing="none">
{t('pages.auth.desc', { APPNAME })}
</TextBlock>
<TextBlock spacing="none">{t('pages.auth.no-pwd-note')}</TextBlock> <TextBlock spacing="none">{t('pages.auth.no-pwd-note')}</TextBlock>
</Content> </Content>
<Actions> <Actions>

View file

@ -5,6 +5,7 @@ import { useAppDispatch } from '../../store';
import apiReducer, { modules } from '../../store/api/reducer'; import apiReducer, { modules } from '../../store/api/reducer';
import SaveButton from '../components/utils/SaveButton'; import SaveButton from '../components/utils/SaveButton';
import { import {
APPNAME,
Field, Field,
FieldNote, FieldNote,
InputBox, InputBox,
@ -53,7 +54,7 @@ export default function ServerSettingsPage(): React.ReactElement {
) )
} }
/> />
<FieldNote>{t('pages.http.bind-help')}</FieldNote> <FieldNote>{t('pages.http.bind-help', { APPNAME })}</FieldNote>
</Field> </Field>
<Field size="fullWidth"> <Field size="fullWidth">
<Label htmlFor="kvpassword"> <Label htmlFor="kvpassword">

View file

@ -13,6 +13,7 @@ import BrowserLink from '../components/BrowserLink';
import DefinitionTable from '../components/DefinitionTable'; import DefinitionTable from '../components/DefinitionTable';
import SaveButton from '../components/utils/SaveButton'; import SaveButton from '../components/utils/SaveButton';
import { import {
APPNAME,
Button, Button,
ButtonGroup, ButtonGroup,
Checkbox, Checkbox,
@ -304,7 +305,6 @@ const TwitchPic = styled('img', {
}); });
const TwitchName = styled('p', { fontWeight: 'bold' }); const TwitchName = styled('p', { fontWeight: 'bold' });
function TwitchEventSubSettings() { function TwitchEventSubSettings() {
const { t } = useTranslation(); const { t } = useTranslation();
const [userStatus, setUserStatus] = useState<helix.User | SyncError>(null); const [userStatus, setUserStatus] = useState<helix.User | SyncError>(null);
@ -337,8 +337,8 @@ function TwitchEventSubSettings() {
}; };
useEffect(() => { useEffect(() => {
// Get user info // Get user info
void getUserInfo(); void getUserInfo();
}, []); }, []);
let userBlock = <i>{t('pages.twitch-settings.events.loading-data')}</i>; let userBlock = <i>{t('pages.twitch-settings.events.loading-data')}</i>;
@ -362,7 +362,7 @@ function TwitchEventSubSettings() {
} }
return ( return (
<> <>
<p>{t('pages.twitch-settings.events.auth-message')}</p> <p>{t('pages.twitch-settings.events.auth-message', { APPNAME })}</p>
<Button <Button
variation="primary" variation="primary"
onClick={() => { onClick={() => {
@ -371,9 +371,13 @@ function TwitchEventSubSettings() {
> >
<ExternalLinkIcon /> {t('pages.twitch-settings.events.auth-button')} <ExternalLinkIcon /> {t('pages.twitch-settings.events.auth-button')}
</Button> </Button>
<SectionHeader>{t('pages.twitch-settings.events.current-status')}</SectionHeader> <SectionHeader>
{t('pages.twitch-settings.events.current-status')}
</SectionHeader>
{userBlock} {userBlock}
<SectionHeader>{t('pages.twitch-settings.events.sim-events')}</SectionHeader> <SectionHeader>
{t('pages.twitch-settings.events.sim-events')}
</SectionHeader>
<ButtonGroup> <ButtonGroup>
{Object.keys(eventsubTests).map((ev: keyof typeof eventsubTests) => ( {Object.keys(eventsubTests).map((ev: keyof typeof eventsubTests) => (
<Button <Button
@ -390,7 +394,6 @@ function TwitchEventSubSettings() {
); );
} }
export default function TwitchSettingsPage(): React.ReactElement { export default function TwitchSettingsPage(): React.ReactElement {
const { t } = useTranslation(); const { t } = useTranslation();
const [twitchConfig, setTwitchConfig] = useModule(modules.twitchConfig); const [twitchConfig, setTwitchConfig] = useModule(modules.twitchConfig);

View file

@ -1,11 +1,14 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT // This file is automatically generated. DO NOT EDIT
import {main} from '../models';
import {helix} from '../models'; import {helix} from '../models';
export function AuthenticateKVClient(arg1:string):Promise<void>; export function AuthenticateKVClient(arg1:string):Promise<void>;
export function GetKilovoltBind():Promise<string>; export function GetKilovoltBind():Promise<string>;
export function GetLastLogs():Promise<Array<main.LogEntry>>;
export function GetTwitchAuthURL():Promise<string>; export function GetTwitchAuthURL():Promise<string>;
export function GetTwitchLoggedUser():Promise<helix.User>; export function GetTwitchLoggedUser():Promise<helix.User>;

View file

@ -10,6 +10,10 @@ export function GetKilovoltBind() {
return window['go']['main']['App']['GetKilovoltBind'](); return window['go']['main']['App']['GetKilovoltBind']();
} }
export function GetLastLogs() {
return window['go']['main']['App']['GetLastLogs']();
}
export function GetTwitchAuthURL() { export function GetTwitchAuthURL() {
return window['go']['main']['App']['GetTwitchAuthURL'](); return window['go']['main']['App']['GetTwitchAuthURL']();
} }

View file

@ -54,3 +54,28 @@ export namespace helix {
} }
export namespace main {
export class LogEntry {
caller: string;
time: string;
level: string;
message: string;
data: string;
static createFrom(source: any = {}) {
return new LogEntry(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.caller = source["caller"];
this.time = source["time"];
this.level = source["level"];
this.message = source["message"];
this.data = source["data"];
}
}
}

1
go.mod
View file

@ -17,6 +17,7 @@ require (
github.com/urfave/cli/v2 v2.23.5 github.com/urfave/cli/v2 v2.23.5
github.com/wailsapp/wails/v2 v2.2.0 github.com/wailsapp/wails/v2 v2.2.0
go.uber.org/zap v1.23.0 go.uber.org/zap v1.23.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
) )
replace github.com/nicklaw5/helix/v2 => github.com/ashkeel/helix/v2 v2.11.0-ws replace github.com/nicklaw5/helix/v2 => github.com/ashkeel/helix/v2 v2.11.0-ws

3
go.sum
View file

@ -35,6 +35,7 @@ git.sr.ht/~hamcha/containers v0.0.3 h1:obG9X8s5iOIahVe+EGpkBDYmUAO78oTi9Y9gRurt3
git.sr.ht/~hamcha/containers v0.0.3/go.mod h1:RiZphUpy9t6EnL4Gf6uzByM9QrBoqRCEPo7kz2wzbhE= git.sr.ht/~hamcha/containers v0.0.3/go.mod h1:RiZphUpy9t6EnL4Gf6uzByM9QrBoqRCEPo7kz2wzbhE=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
@ -831,6 +832,8 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

104
logging.go Normal file
View file

@ -0,0 +1,104 @@
package main
import (
"os"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
var logger *zap.Logger
const LogHistory = 50
var (
lastLogs []LogEntry
incomingLogs chan LogEntry
)
func initLogger() {
lastLogs = make([]LogEntry, 0)
incomingLogs = make(chan LogEntry, 100)
logStorage := NewLogStorage(zap.InfoLevel)
consoleLogger := zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
zapcore.Lock(os.Stderr),
zap.InfoLevel,
)
fileLogger := zapcore.NewCore(
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
zapcore.AddSync(&lumberjack.Logger{
Filename: "strimertul.log",
MaxSize: 500,
MaxBackups: 3,
MaxAge: 28,
}),
zap.DebugLevel,
)
core := zapcore.NewTee(
consoleLogger,
fileLogger,
logStorage,
)
logger = zap.New(core, zap.AddCaller())
}
type LogEntry struct {
Caller string `json:"caller"`
Time string `json:"time"`
Level string `json:"level"`
Message string `json:"message"`
Data string `json:"data"`
}
type LogStorage struct {
zapcore.LevelEnabler
fields []zapcore.Field
encoder zapcore.Encoder
}
func NewLogStorage(enabler zapcore.LevelEnabler) *LogStorage {
return &LogStorage{
LevelEnabler: enabler,
encoder: zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig()),
}
}
func (core *LogStorage) With(fields []zapcore.Field) zapcore.Core {
clone := *core
clone.fields = append(clone.fields, fields...)
return &clone
}
func (core *LogStorage) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if core.Enabled(entry.Level) {
return checked.AddCore(entry, core)
}
return checked
}
func (core *LogStorage) Write(entry zapcore.Entry, fields []zapcore.Field) error {
buf, err := core.encoder.EncodeEntry(entry, append(core.fields, fields...))
if err != nil {
return err
}
logEntry := LogEntry{
Caller: entry.Caller.String(),
Time: entry.Time.Format(time.RFC3339),
Level: entry.Level.String(),
Message: entry.Message,
Data: buf.String(),
}
lastLogs = append(lastLogs, logEntry)
if len(lastLogs) > LogHistory {
lastLogs = lastLogs[1:]
}
incomingLogs <- logEntry
return nil
}
func (core *LogStorage) Sync() error {
return nil
}

34
main.go
View file

@ -8,18 +8,16 @@ import (
"os" "os"
"time" "time"
jsoniter "github.com/json-iterator/go" "github.com/strimertul/strimertul/modules"
"github.com/strimertul/strimertul/modules/loyalty"
"github.com/strimertul/strimertul/modules/twitch"
jsoniter "github.com/json-iterator/go"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/assetserver"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/strimertul/strimertul/modules"
"github.com/strimertul/strimertul/modules/loyalty"
"github.com/strimertul/strimertul/modules/twitch"
_ "net/http/pprof" _ "net/http/pprof"
) )
@ -30,8 +28,6 @@ const databaseDefaultDriver = "pebble"
var appVersion = "v0.0.0-UNKNOWN" var appVersion = "v0.0.0-UNKNOWN"
var logger *zap.Logger
//go:embed frontend/dist/* //go:embed frontend/dist/*
var frontend embed.FS var frontend embed.FS
@ -49,8 +45,6 @@ func main() {
Version: appVersion, Version: appVersion,
Action: cliMain, Action: cliMain,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{Name: "debug", Aliases: []string{"d"}, Usage: "print more logs (for debugging)", Value: false},
&cli.BoolFlag{Name: "json-log", Usage: "print logs in JSON format", Value: false},
&cli.StringFlag{Name: "driver", Usage: "specify database driver", Value: "auto"}, &cli.StringFlag{Name: "driver", Usage: "specify database driver", Value: "auto"},
&cli.StringFlag{Name: "database-dir", Aliases: []string{"db-dir"}, Usage: "specify database directory", Value: "data"}, &cli.StringFlag{Name: "database-dir", Aliases: []string{"db-dir"}, Usage: "specify database directory", Value: "data"},
&cli.StringFlag{Name: "backup-dir", Aliases: []string{"b-dir"}, Usage: "specify backup directory", Value: "backups"}, &cli.StringFlag{Name: "backup-dir", Aliases: []string{"b-dir"}, Usage: "specify backup directory", Value: "backups"},
@ -91,7 +85,7 @@ func main() {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
// Initialize logger with global flags // Initialize logger with global flags
initLogger(ctx.Bool("debug"), ctx.Bool("json-log")) initLogger()
return nil return nil
}, },
After: func(ctx *cli.Context) error { After: func(ctx *cli.Context) error {
@ -106,24 +100,6 @@ func main() {
} }
} }
func initLogger(debug bool, json bool) {
if debug {
cfg := zap.NewDevelopmentConfig()
if json {
cfg.Encoding = "json"
}
logger, _ = cfg.Build()
} else {
cfg := zap.NewProductionConfig()
if !json {
cfg.Encoding = "console"
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
cfg.EncoderConfig.CallerKey = zapcore.OmitKey
}
logger, _ = cfg.Build()
}
}
func cliMain(ctx *cli.Context) error { func cliMain(ctx *cli.Context) error {
// Create an instance of the app structure // Create an instance of the app structure
app := NewApp(ctx) app := NewApp(ctx)