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:
parent
0a3b6eae4c
commit
bb8f3f754e
16 changed files with 310 additions and 78 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,3 +7,4 @@
|
|||
.idea
|
||||
strimertul_*
|
||||
build/bin
|
||||
*.log
|
27
app.go
27
app.go
|
@ -4,14 +4,15 @@ import (
|
|||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/nicklaw5/helix/v2"
|
||||
|
||||
"github.com/strimertul/strimertul/modules"
|
||||
"github.com/strimertul/strimertul/modules/database"
|
||||
"github.com/strimertul/strimertul/modules/http"
|
||||
"github.com/strimertul/strimertul/modules/twitch"
|
||||
|
||||
"git.sr.ht/~hamcha/containers"
|
||||
"github.com/nicklaw5/helix/v2"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -21,12 +22,14 @@ type App struct {
|
|||
cliParams *cli.Context
|
||||
driver DatabaseDriver
|
||||
manager *modules.Manager
|
||||
ready *containers.RWSync[bool]
|
||||
}
|
||||
|
||||
// NewApp creates a new App application struct
|
||||
func NewApp(cliParams *cli.Context) *App {
|
||||
return &App{
|
||||
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
|
||||
go failOnError(httpServer.Listen(), "HTTP server stopped")
|
||||
failOnError(httpServer.Listen(), "HTTP server stopped")
|
||||
}
|
||||
|
||||
func (a *App) stop(context.Context) {
|
||||
|
@ -101,6 +115,9 @@ func (a *App) AuthenticateKVClient(id string) {
|
|||
}
|
||||
|
||||
func (a *App) IsServerReady() bool {
|
||||
if !a.ready.Get() {
|
||||
return false
|
||||
}
|
||||
return a.manager.Modules[modules.ModuleHTTP].Status().Working
|
||||
}
|
||||
|
||||
|
@ -121,3 +138,7 @@ func (a *App) GetTwitchAuthURL() string {
|
|||
func (a *App) GetTwitchLoggedUser() (helix.User, error) {
|
||||
return a.manager.Modules[modules.ModuleTwitch].(*twitch.Client).GetLoggedUser()
|
||||
}
|
||||
|
||||
func (a *App) GetLastLogs() []LogEntry {
|
||||
return lastLogs
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"bind": "Webserver Bind",
|
||||
"kilovolt-password": "Kilovolt password",
|
||||
"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-placeholder": "Absolute path to static assets",
|
||||
"static-help": "Will be served at the following URL: {{url}}",
|
||||
|
@ -82,7 +82,7 @@
|
|||
},
|
||||
"sim-events": "Send test event",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
|
@ -130,7 +130,7 @@
|
|||
},
|
||||
"auth": {
|
||||
"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.",
|
||||
"password": "Password",
|
||||
"submit": "Authenticate"
|
||||
|
@ -284,6 +284,7 @@
|
|||
"wip": {
|
||||
"header": "WIP - Page under development",
|
||||
"text": "This page is still under construction, apologies for the lackluster view :("
|
||||
}
|
||||
},
|
||||
"loading": "{{APPNAME}} is starting up, please wait!"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ import { configureStore } from '@reduxjs/toolkit';
|
|||
import { useDispatch } from 'react-redux';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import apiReducer from './api/reducer';
|
||||
import loggingReducer from './logging/reducer';
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
api: apiReducer.reducer,
|
||||
logging: loggingReducer.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
|
|
53
frontend/src/store/logging/reducer.ts
Normal file
53
frontend/src/store/logging/reducer.ts
Normal 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;
|
|
@ -1,5 +1,3 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
ChatBubbleIcon,
|
||||
DashboardIcon,
|
||||
|
@ -10,34 +8,45 @@ import {
|
|||
TableIcon,
|
||||
TimerIcon,
|
||||
} 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 { ToastContainer } from 'react-toastify';
|
||||
import { GetKilovoltBind } from '@wailsapp/go/main/App';
|
||||
|
||||
import { delay } from 'src/lib/time-utils';
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import Sidebar, { RouteSection } from './components/Sidebar';
|
||||
import ServerSettingsPage from './pages/ServerSettings';
|
||||
import {
|
||||
GetKilovoltBind,
|
||||
GetLastLogs,
|
||||
IsServerReady,
|
||||
} from '@wailsapp/go/main/App';
|
||||
import { main } from '@wailsapp/go/models';
|
||||
|
||||
import { RootState, useAppDispatch } from '../store';
|
||||
import { createWSClient, useAuthBypass } from '../store/api/reducer';
|
||||
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
|
||||
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', {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: '100vh',
|
||||
|
@ -47,10 +56,15 @@ const Spinner = styled('img', {
|
|||
maxWidth: '100px',
|
||||
});
|
||||
|
||||
function Loading() {
|
||||
interface LoadingProps {
|
||||
message: string;
|
||||
}
|
||||
|
||||
function Loading({ message }: React.PropsWithChildren<LoadingProps>) {
|
||||
return (
|
||||
<LoadingDiv>
|
||||
<Spinner src={spinner as string} alt="Loading..." />
|
||||
<p>{message}</p>
|
||||
</LoadingDiv>
|
||||
);
|
||||
}
|
||||
|
@ -143,6 +157,7 @@ const PageWrapper = styled('div', {
|
|||
});
|
||||
|
||||
export default function App(): JSX.Element {
|
||||
const [ready, setReady] = useState(false);
|
||||
const client = useSelector((state: RootState) => state.api.client);
|
||||
const connected = useSelector(
|
||||
(state: RootState) => state.api.connectionStatus,
|
||||
|
@ -150,16 +165,7 @@ export default function App(): JSX.Element {
|
|||
const dispatch = useAppDispatch();
|
||||
|
||||
const connectToKV = async () => {
|
||||
let address = '';
|
||||
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);
|
||||
}
|
||||
}
|
||||
const address = await GetKilovoltBind();
|
||||
await dispatch(
|
||||
createWSClient({
|
||||
address: `ws://${address}/ws`,
|
||||
|
@ -167,17 +173,43 @@ export default function App(): JSX.Element {
|
|||
);
|
||||
};
|
||||
|
||||
// Get application logs
|
||||
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) {
|
||||
void connectToKV();
|
||||
}
|
||||
if (connected === ConnectionStatus.AuthenticationNeeded) {
|
||||
void dispatch(useAuthBypass());
|
||||
}
|
||||
});
|
||||
}, [ready, connected]);
|
||||
|
||||
if (connected === ConnectionStatus.NotConnected) {
|
||||
return <Loading />;
|
||||
return <Loading message={t('special.loading', { APPNAME })} />;
|
||||
}
|
||||
|
||||
if (connected === ConnectionStatus.AuthenticationNeeded) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { styled } from '@stitches/react';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, InputBox, TextBlock } from '../theme';
|
||||
import { APPNAME, Button, InputBox, TextBlock } from '../theme';
|
||||
|
||||
const AuthWrapper = styled('div', {
|
||||
alignItems: 'center',
|
||||
|
@ -59,7 +59,9 @@ export default function AuthDialog(): React.ReactElement {
|
|||
>
|
||||
<AuthTitle>{t('pages.auth.title')}</AuthTitle>
|
||||
<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>
|
||||
</Content>
|
||||
<Actions>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useAppDispatch } from '../../store';
|
|||
import apiReducer, { modules } from '../../store/api/reducer';
|
||||
import SaveButton from '../components/utils/SaveButton';
|
||||
import {
|
||||
APPNAME,
|
||||
Field,
|
||||
FieldNote,
|
||||
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 size="fullWidth">
|
||||
<Label htmlFor="kvpassword">
|
||||
|
|
|
@ -13,6 +13,7 @@ import BrowserLink from '../components/BrowserLink';
|
|||
import DefinitionTable from '../components/DefinitionTable';
|
||||
import SaveButton from '../components/utils/SaveButton';
|
||||
import {
|
||||
APPNAME,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Checkbox,
|
||||
|
@ -304,7 +305,6 @@ const TwitchPic = styled('img', {
|
|||
});
|
||||
const TwitchName = styled('p', { fontWeight: 'bold' });
|
||||
|
||||
|
||||
function TwitchEventSubSettings() {
|
||||
const { t } = useTranslation();
|
||||
const [userStatus, setUserStatus] = useState<helix.User | SyncError>(null);
|
||||
|
@ -337,8 +337,8 @@ function TwitchEventSubSettings() {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Get user info
|
||||
void getUserInfo();
|
||||
// Get user info
|
||||
void getUserInfo();
|
||||
}, []);
|
||||
|
||||
let userBlock = <i>{t('pages.twitch-settings.events.loading-data')}</i>;
|
||||
|
@ -362,7 +362,7 @@ function TwitchEventSubSettings() {
|
|||
}
|
||||
return (
|
||||
<>
|
||||
<p>{t('pages.twitch-settings.events.auth-message')}</p>
|
||||
<p>{t('pages.twitch-settings.events.auth-message', { APPNAME })}</p>
|
||||
<Button
|
||||
variation="primary"
|
||||
onClick={() => {
|
||||
|
@ -371,9 +371,13 @@ function TwitchEventSubSettings() {
|
|||
>
|
||||
<ExternalLinkIcon /> {t('pages.twitch-settings.events.auth-button')}
|
||||
</Button>
|
||||
<SectionHeader>{t('pages.twitch-settings.events.current-status')}</SectionHeader>
|
||||
<SectionHeader>
|
||||
{t('pages.twitch-settings.events.current-status')}
|
||||
</SectionHeader>
|
||||
{userBlock}
|
||||
<SectionHeader>{t('pages.twitch-settings.events.sim-events')}</SectionHeader>
|
||||
<SectionHeader>
|
||||
{t('pages.twitch-settings.events.sim-events')}
|
||||
</SectionHeader>
|
||||
<ButtonGroup>
|
||||
{Object.keys(eventsubTests).map((ev: keyof typeof eventsubTests) => (
|
||||
<Button
|
||||
|
@ -390,7 +394,6 @@ function TwitchEventSubSettings() {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
export default function TwitchSettingsPage(): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const [twitchConfig, setTwitchConfig] = useModule(modules.twitchConfig);
|
||||
|
|
3
frontend/wailsjs/go/main/App.d.ts
vendored
3
frontend/wailsjs/go/main/App.d.ts
vendored
|
@ -1,11 +1,14 @@
|
|||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {main} from '../models';
|
||||
import {helix} from '../models';
|
||||
|
||||
export function AuthenticateKVClient(arg1:string):Promise<void>;
|
||||
|
||||
export function GetKilovoltBind():Promise<string>;
|
||||
|
||||
export function GetLastLogs():Promise<Array<main.LogEntry>>;
|
||||
|
||||
export function GetTwitchAuthURL():Promise<string>;
|
||||
|
||||
export function GetTwitchLoggedUser():Promise<helix.User>;
|
||||
|
|
|
@ -10,6 +10,10 @@ export function GetKilovoltBind() {
|
|||
return window['go']['main']['App']['GetKilovoltBind']();
|
||||
}
|
||||
|
||||
export function GetLastLogs() {
|
||||
return window['go']['main']['App']['GetLastLogs']();
|
||||
}
|
||||
|
||||
export function GetTwitchAuthURL() {
|
||||
return window['go']['main']['App']['GetTwitchAuthURL']();
|
||||
}
|
||||
|
|
|
@ -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
1
go.mod
|
@ -17,6 +17,7 @@ require (
|
|||
github.com/urfave/cli/v2 v2.23.5
|
||||
github.com/wailsapp/wails/v2 v2.2.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
|
||||
|
|
3
go.sum
3
go.sum
|
@ -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=
|
||||
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 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/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=
|
||||
|
@ -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/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
104
logging.go
Normal file
104
logging.go
Normal 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
34
main.go
|
@ -8,18 +8,16 @@ import (
|
|||
"os"
|
||||
"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/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
"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"
|
||||
)
|
||||
|
@ -30,8 +28,6 @@ const databaseDefaultDriver = "pebble"
|
|||
|
||||
var appVersion = "v0.0.0-UNKNOWN"
|
||||
|
||||
var logger *zap.Logger
|
||||
|
||||
//go:embed frontend/dist/*
|
||||
var frontend embed.FS
|
||||
|
||||
|
@ -49,8 +45,6 @@ func main() {
|
|||
Version: appVersion,
|
||||
Action: cliMain,
|
||||
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: "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"},
|
||||
|
@ -91,7 +85,7 @@ func main() {
|
|||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Initialize logger with global flags
|
||||
initLogger(ctx.Bool("debug"), ctx.Bool("json-log"))
|
||||
initLogger()
|
||||
return nil
|
||||
},
|
||||
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 {
|
||||
// Create an instance of the app structure
|
||||
app := NewApp(ctx)
|
||||
|
|
Loading…
Reference in a new issue