implement CBA macros, fix for prod

- using a3go 0.3.2, no longer relies on ext callback for anything except RPT logging and waiting DB connect at postinit
- tested and functional
This commit is contained in:
2023-10-12 15:41:22 -07:00
parent 62fbe8b24c
commit 6cf76d1019
29 changed files with 487 additions and 452 deletions

2
.gitignore vendored
View File

@@ -11,3 +11,5 @@ hemtt.exe
*.bk *.bk
releases/ releases/
mariadb/db/
AttendanceTracker.config.json

View File

@@ -10,6 +10,7 @@ git_hash=6 # Default: 8
[files] [files]
include=[ include=[
"AttendanceTracker.config.example.json", "AttendanceTracker.config.example.json",
# "AttendanceTracker.config.json", # used for copying active config during debugging
"LICENSE", "LICENSE",
"README", "README",
"mod.cpp", "mod.cpp",

View File

@@ -3,12 +3,12 @@
"mysqlHost": "localhost", "mysqlHost": "localhost",
"mysqlPort": 3306, "mysqlPort": 3306,
"mysqlUser": "root", "mysqlUser": "root",
"mysqlPassword": "password", "mysqlPassword": "example",
"mysqlDatabase": "a3attendance" "mysqlDatabase": "a3attendance"
}, },
"armaConfig": { "armaConfig": {
"dbUpdateInterval": "90s", "dbUpdateInterval": "90s",
"debug": false, "debug": false,
"traceLogToFile": false "trace": false
} }
} }

View File

@@ -166,7 +166,7 @@ docker pull x1unix/go-mingw:1.20
# version is semantic + build date + git hash # version is semantic + build date + git hash
# e.g. 1.0.0-2021-05-30-1a2b3c4d # e.g. 1.0.0-2021-05-30-1a2b3c4d
$versionSem = '1.0.1' $versionSem = '1.1.1'
$dateStr = Get-Date -Format 'yyyyMMdd' $dateStr = Get-Date -Format 'yyyyMMdd'
$version = "$versionSem-$dateStr-$(git rev-parse --short HEAD)" $version = "$versionSem-$dateStr-$(git rev-parse --short HEAD)"

View File

@@ -1,7 +1,7 @@
#include "script_mod.hpp" #include "script_component.hpp"
class CfgPatches { class CfgPatches {
class AttendanceTracker { class ADDON {
units[] = {}; units[] = {};
weapons[] = {}; weapons[] = {};
requiredVersion = 2.10; requiredVersion = 2.10;
@@ -17,20 +17,17 @@ class CfgPatches {
}; };
class CfgFunctions { class CfgFunctions {
class attendanceTracker { class ADDON {
class functions { class functions {
file = "x\addons\attendancetracker\main\functions"; class postInit {
class postInit {postInit = 1;}; file = QPATHTOF(DOUBLES(fnc,postInit).sqf);
class callbackHandler {postInit = 1;}; postInit = 1;
class getMissionHash {}; };
class getMissionInfo {}; PATHTO_FNC(getMissionInfo);
class getSettings {}; PATHTO_FNC(getWorldInfo);
class getWorldInfo {}; PATHTO_FNC(log);
class log {}; PATHTO_FNC(missionLoaded);
class missionLoaded {}; PATHTO_FNC(onPlayerConnected);
class onPlayerConnected {};
class timestamp {};
class writePlayer {};
}; };
}; };
}; };

View File

@@ -0,0 +1,13 @@
#include "script_component.hpp"
[
["missionName", missionName],
["missionStart", GVAR(missionStart)],
["missionHash", GVAR(missionHash)],
["briefingName", briefingName],
["missionNameSource", missionNameSource],
["onLoadName", getMissionConfigValue ["onLoadName", "Unknown"]],
["author", getMissionConfigValue ["author", "Unknown"]],
["serverName", serverName],
["serverProfile", profileName],
["worldName", toLower worldName]
];

View File

@@ -1,3 +1,5 @@
#include "script_component.hpp"
_world = ( configfile >> "CfgWorlds" >> worldName ); _world = ( configfile >> "CfgWorlds" >> worldName );
_author = getText( _world >> "author" ); _author = getText( _world >> "author" );
_name = getText ( _world >> "description" ); _name = getText ( _world >> "description" );
@@ -5,7 +7,6 @@ _name = getText ( _world >> "description" );
_source = configSourceMod ( _world ); _source = configSourceMod ( _world );
_workshopID = ''; _workshopID = '';
{ {
if ( ( _x#1 ) == _source ) then { if ( ( _x#1 ) == _source ) then {
_workshopID = _x#7; _workshopID = _x#7;
@@ -13,8 +14,12 @@ _workshopID = '';
}; };
} foreach getLoadedModsInfo; } foreach getLoadedModsInfo;
if (_workshopID isEqualTo "") then {
_workshopID = "0";
};
// [_name, _author, _workshopID]; // [_name, _author, _workshopID];
_return = createHashMapFromArray [ _return = [
["author", _author], ["author", _author],
["workshopID", _workshopID], ["workshopID", _workshopID],
["displayName", _name], ["displayName", _name],
@@ -24,5 +29,5 @@ _return = createHashMapFromArray [
["latitude", -1 * getNumber( _world >> "latitude" )], ["latitude", -1 * getNumber( _world >> "latitude" )],
["longitude", getNumber( _world >> "longitude" )] ["longitude", getNumber( _world >> "longitude" )]
]; ];
[format["WorldInfo is: %1", _return]] call attendanceTracker_fnc_log; ["DEBUG", format["WorldInfo is: %1", _return]] call FUNC(log);
_return _return

31
addons/main/fnc_log.sqf Normal file
View File

@@ -0,0 +1,31 @@
#include "script_component.hpp"
if (!isServer) exitWith {};
if (typeName _this != "ARRAY") exitWith {
diag_log format ["[%1]: Invalid log params: %2", GVAR(logPrefix), _this];
};
params [
["_level", "INFO", [""]],
["_text", "", ["", []]]
];
if (_text isEqualType []) then {
_text = format ["%1", _text];
};
if (
_level == "DEBUG" &&
!GVAR(debug)
) exitWith {};
if (_text isEqualTo "") exitWith {};
diag_log formatText [
"[%1] %2: %3",
GVAR(logPrefix),
_level,
_text
];

View File

@@ -1 +1,3 @@
#include "script_component.hpp"
!(getClientStateNumber <= 5 || getClientStateNumber isEqualTo 11); !(getClientStateNumber <= 5 || getClientStateNumber isEqualTo 11);

View File

@@ -0,0 +1,84 @@
#include "script_component.hpp"
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
["DEBUG", format ["(EventHandler) PlayerConnected fired: %1", _this]] call FUNC(log);
if !(call FUNC(missionLoaded)) exitWith {
["DEBUG", format ["(EventHandler) PlayerConnected: Server is in Mission Asked, likely mission selection state. Skipping.."]] call FUNC(log);
};
private _userInfo = (getUserInfo _idstr);
if ((count _userInfo) isEqualTo 0) exitWith {
["DEBUG", format ["(EventHandler) PlayerConnected: No user info found for %1", _idstr]] call FUNC(log);
};
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
if (_isHC) exitWith {
[
"DEBUG",
format [
"(EventHandler) PlayerConnected: %1 is HC, skipping",
_playerID
]
] call FUNC(log);
};
// start CBA PFH
[
"DEBUG",
format [
"(EventHandler) PlayerConnected: Starting CBA PFH for %1",
_playerID
]
] call FUNC(log);
[{
params ["_args", "_handle"];
// every dbUpdateInterval, queue a wait for the mission to be logged
// times out after 30 seconds
// used to ensure joins at start of mission (during db connect) are logged
[{GVAR(missionLogged)}, {
// check if player is still connected
private _hash = _this;
private _clientStateNumber = 0;
private _userInfo = getUserInfo (_hash get "playerId");
if (_userInfo isEqualTo []) exitWith {
["DEBUG", format ["(EventHandler) PlayerConnected: %1 (UID) is no longer connected to the mission, exiting CBA PFH", _hash get "playerUID"]] call FUNC(log);
[_handle] call CBA_fnc_removePerFrameHandler;
};
_clientStateNumber = _userInfo select 6;
if (_clientStateNumber < 6) exitWith {
["DEBUG", format ["(EventHandler) PlayerConnected: %1 (UID) is no longer connected to the mission, exiting CBA PFH", _hash get "playerUID"]] call FUNC(log);
[_handle] call CBA_fnc_removePerFrameHandler;
};
["DEBUG", format [
"(EventHandler) PlayerConnected: %1 (UID) is connected to the mission, logging. data: %2",
_hash get "playerUID",
_hash
]] call FUNC(log);
GVAR(extensionName) callExtension [
":LOG:PRESENCE:", [
_hash
]];
},
_args, // args
30 // timeout
] call CBA_fnc_waitUntilAndExecute;
},
GVAR(updateInterval),
(createHashMapFromArray [ // args
["playerId", _playerID],
["playerUID", _playerUID],
["profileName", _profileName],
["steamName", _steamName],
["isJIP", _jip],
["roleDescription", if (roleDescription _unit isEqualTo "") then {"None"} else {roleDescription _unit}],
["missionHash", GVAR(missionHash)]
])
] call CBA_fnc_addPerFrameHandler;

View File

@@ -0,0 +1,94 @@
#include "script_component.hpp"
if (!isServer) exitWith {};
GVAR(attendanceTracker) = true;
GVAR(debug) = true;
GVAR(logPrefix) = "AttendanceTracker";
GVAR(extensionName) = "AttendanceTracker";
GVAR(missionLogged) = false;
addMissionEventHandler ["ExtensionCallback", {
params ["_name", "_function", "_data"];
if !(_name isEqualTo GVAR(extensionName)) exitWith {};
_dataArr = parseSimpleArray _data;
if (count _dataArr isEqualTo 0) exitWith {};
switch (_function) do {
case ":LOG:MISSION:SUCCESS:": {
GVAR(missionLogged) = true;
};
case ":LOG:": {
diag_log formatText[
"[%1] %2",
GVAR(logPrefix),
_dataArr select 0
];
};
default {
["DEBUG", format["%1", _dataArr]] call FUNC(log);
};
};
}];
// LOAD EXTENSION
GVAR(extensionName) callExtension ":START:";
// GET MISSION START TIMESTAMP AND UNIQUE HASH
private _missionHashData = parseSimpleArray ("AttendanceTracker" callExtension ":MISSION:HASH:");
if (count _missionHashData isEqualTo 0) exitWith {
["ERROR", "Failed to get mission hash, exiting"] call FUNC(log);
};
_missionHashData params ["_timestamp", "_hash"];
GVAR(missionStart) = _timestamp;
GVAR(missionHash) = _hash;
// PARSE SETTINGS
private _settings = parseSimpleArray (GVAR(extensionName) callExtension ":GET:SETTINGS:");
if (count _settings isEqualTo 0) exitWith {
["ERROR", "Failed to get settings, exiting"] call FUNC(log);
};
GVAR(settings) = createHashMapFromArray (_settings#0);
GVAR(debug) = GVAR(settings) getOrDefault ["debug", GVAR(debug)];
private _updateInterval = GVAR(settings) getOrDefault ["dbupdateinterval", 90];
// remove duration by removing the last index
_updateInterval = _updateInterval select [0, count _updateInterval - 1];
GVAR(updateInterval) = parseNumber _updateInterval;
// add player connected (to mission) handler
addMissionEventHandler ["PlayerConnected", {
_this call FUNC(onPlayerConnected);
}];
// we'll wait for the end of init (DB connect included) of the extension
// then we'll log the world and mission
// the response to THAT is handled above in the extension callback
// and will set GVAR(missionLogged) true
addMissionEventHandler ["ExtensionCallback", {
params ["_name", "_function", "_data"];
if !(_name isEqualTo GVAR(extensionName)) exitWith {};
if !(_function isEqualTo ":READY:") exitWith {};
// LOAD WORLD AND MISSION INFO
GVAR(worldInfo) = call FUNC(getWorldInfo);
GVAR(missionInfo) = call FUNC(getMissionInfo);
["INFO", (GVAR(extensionName) callExtension [
":LOG:MISSION:",
[
GVAR(worldInfo),
GVAR(missionInfo)
]
]) select 0] call FUNC(log);
// remove the handler
removeMissionEventHandler ["ExtensionCallback", _thisEventHandler];
}];

View File

@@ -1,24 +0,0 @@
addMissionEventHandler ["ExtensionCallback", {
params ["_name", "_function", "_data"];
if !(_name isEqualTo "AttendanceTracker") exitWith {};
if (ATDebug && _function isNotEqualTo ":LOG:") then {
diag_log format ["Raw callback: %1 _ %2", _function, _data];
};
_dataArr = parseSimpleArray _data;
if (count _dataArr < 1) exitWith {};
switch (_function) do {
case ":LOG:": {
diag_log formatText[
"[Attendance Tracker] %1",
_dataArr select 0
];
};
default {
[format["%1", _dataArr]] call attendanceTracker_fnc_log;
};
};
true;
}];

View File

@@ -1,19 +0,0 @@
addMissionEventHandler ["ExtensionCallback", {
params ["_extension", "_function", "_data"];
if !(_extension isEqualTo "AttendanceTracker") exitWith {};
if !(_function isEqualTo ":MISSION:HASH:") exitWith {};
_dataArr = parseSimpleArray _data;
if (count _dataArr < 1) exitWith {};
_dataArr params ["_startTime", "_hash"];
ATNamespace setVariable ["missionStartTime", call attendanceTracker_fnc_timestamp];
ATNamespace setVariable ["missionHash", _hash];
removeMissionEventHandler [
"ExtensionCallback",
_thisEventHandler
];
}];
"AttendanceTracker" callExtension ":MISSION:HASH:";

View File

@@ -1,12 +0,0 @@
createHashMapFromArray [
["missionName", missionName],
["missionStart", ATNamespace getVariable "missionStartTime"],
["missionHash", ATNamespace getVariable "missionHash"],
["briefingName", briefingName],
["missionNameSource", missionNameSource],
["onLoadName", getMissionConfigValue ["onLoadName", ""]],
["author", getMissionConfigValue ["author", ""]],
["serverName", serverName],
["serverProfile", profileName],
["worldName", toLower worldName]
];

View File

@@ -1,27 +0,0 @@
addMissionEventHandler ["ExtensionCallback", {
params ["_extension", "_function", "_data"];
if !(_extension isEqualTo "AttendanceTracker") exitWith {};
if !(_function isEqualTo ":GET:SETTINGS:") exitWith {};
_dataArr = parseSimpleArray _data;
diag_log format ["AT: Settings received: %1", _dataArr];
if (count _dataArr < 1) exitWith {};
private _settingsJSON = _dataArr select 0;
private _settingsNamespace = [_settingsJSON] call CBA_fnc_parseJSON;
{
ATNamespace setVariable [_x, _settingsNamespace getVariable _x];
} forEach (allVariables _settingsNamespace);
ATDebug = ATNamespace getVariable "debug";
ATUpdateDelay = ATNamespace getVariable "dbUpdateInterval";
// remove last character (unit of time) and parse to number
ATUpdateDelay = parseNumber (ATUpdateDelay select [0, count ATUpdateDelay - 1]);
removeMissionEventHandler [
"ExtensionCallback",
_thisEventHandler
];
}];
"AttendanceTracker" callExtension ":GET:SETTINGS:";

View File

@@ -1,17 +0,0 @@
#include "..\script_mod.hpp"
params [
["_message", "", [""]],
["_level", "INFO", [""]],
"_function"
];
if (isNil "_message") exitWith {false};
if (
missionNamespace getVariable ["ATDebug", true] &&
_level != "WARN" && _level != "ERROR"
) exitWith {};
LOG_SYS(_level, _message);
true;

View File

@@ -1,63 +0,0 @@
params ["_id", "_uid", "_name", "_jip", "_owner", "_idstr"];
[format ["(EventHandler) PlayerConnected fired: %1", _this], "DEBUG"] call attendanceTracker_fnc_log;
if !(call attendanceTracker_fnc_missionLoaded) exitWith {
[format ["(EventHandler) PlayerConnected: Server is in Mission Asked, likely mission selection state. Skipping.."], "DEBUG"] call attendanceTracker_fnc_log;
};
private _userInfo = (getUserInfo _idstr);
if ((count _userInfo) isEqualTo 0) exitWith {
[format ["(EventHandler) PlayerConnected: No user info found for %1", _idstr], "DEBUG"] call attendanceTracker_fnc_log;
};
_userInfo params ["_playerID", "_ownerId", "_playerUID", "_profileName", "_displayName", "_steamName", "_clientState", "_isHC", "_adminState", "_networkInfo", "_unit"];
if (_isHC) exitWith {
[
format [
"(EventHandler) PlayerConnected: %1 is HC, skipping",
_playerID
],
"DEBUG"
] call attendanceTracker_fnc_log;
};
// start CBA PFH
[
format [
"(EventHandler) PlayerConnected: Starting CBA PFH for %1",
_playerID
],
"DEBUG"
] call attendanceTracker_fnc_log;
[
{
params ["_args", "_handle"];
// check if player is still connected
_args params ["_playerID", "_playerUID", "_profileName", "_steamName", "_jip", "_roleDescription"];
private _userInfo = getUserInfo _playerID;
private _clientStateNumber = 0;
if (_userInfo isEqualTo []) exitWith {
[_handle] call CBA_fnc_removePerFrameHandler;
};
_clientStateNumber = _userInfo select 6;
if (_clientStateNumber < 6) exitWith {
[format ["(EventHandler) PlayerConnected: %1 (UID) is no longer connected to the mission, exiting CBA PFH", _playerID], "DEBUG"] call attendanceTracker_fnc_log;
[_handle] call CBA_fnc_removePerFrameHandler;
};
_args call attendanceTracker_fnc_writePlayer;
},
ATUpdateDelay,
[
_playerID,
_playerUID,
_profileName,
_steamName,
_jip,
roleDescription _unit
]
] call CBA_fnc_addPerFrameHandler;

View File

@@ -1,61 +0,0 @@
#include "..\script_mod.hpp"
if (!isServer) exitWith {};
ATNamespace = false call CBA_fnc_createNamespace;
ATDebug = true;
"AttendanceTracker" callExtension ":START:";
// we'll wait for the asynchronous init steps of the extension to finish, to confirm we have a DB connection and our config was loaded. If there are errors with either, the extension won't reply and initiate further during this mission.
addMissionEventHandler ["ExtensionCallback", {
params ["_name", "_function", "_data"];
if !(_name isEqualTo "AttendanceTracker") exitWith {};
if !(_function isEqualTo ":READY:") exitWith {};
call attendanceTracker_fnc_getMissionHash;
call attendanceTracker_fnc_getSettings;
[
{// wait until settings have been loaded from extension
!isNil {ATNamespace getVariable "missionHash"} &&
!isNil {ATDebug}
},
{
// get world and mission context
ATNamespace setVariable [
"worldContext",
call attendanceTracker_fnc_getWorldInfo
];
ATNamespace setVariable [
"missionContext",
call attendanceTracker_fnc_getMissionInfo
];
// write them to establish DB rows
"AttendanceTracker" callExtension [
":LOG:MISSION:",
[
[ATNamespace getVariable "missionContext"] call CBA_fnc_encodeJSON,
[ATNamespace getVariable "worldContext"] call CBA_fnc_encodeJSON
]
];
// add player connected (to mission) handler
addMissionEventHandler ["PlayerConnected", {
_this call attendanceTracker_fnc_onPlayerConnected;
}];
},
[],
10, // 10 second timeout
{ // timeout code
["Failed to load settings", "ERROR"] call attendanceTracker_fnc_log;
}
] call CBA_fnc_waitUntilAndExecute;
removeMissionEventHandler [
"ExtensionCallback",
_thisEventHandler
];
}];

View File

@@ -1,24 +0,0 @@
// (parseSimpleArray ("AttendanceTracker" callExtension "getTimestamp")) select 0;
// const time.RFC3339 untyped string = "2006-01-02T15:04:05Z07:00"
systemTimeUTC apply {if (_x < 10) then {"0" + str _x} else {str _x}} params [
"_year",
"_month",
"_day",
"_hour",
"_minute",
"_second",
"_millisecond"
];
format[
"%1-%2-%3T%4:%5:%6Z",
_year,
_month,
_day,
_hour,
_minute,
_second
];

View File

@@ -1,21 +0,0 @@
params [
["_playerId", ""],
["_playerUID", ""],
["_profileName", ""],
["_steamName", ""],
["_isJIP", false, [true, false]],
["_roleDescription", ""]
];
private _hash = +(ATNamespace getVariable ["missionContext", createHashMap]);
_hash set ["playerId", _playerId];
_hash set ["playerUID", _playerUID];
_hash set ["profileName", _profileName];
_hash set ["steamName", _steamName];
_hash set ["isJIP", _isJIP];
_hash set ["roleDescription", _roleDescription];
"AttendanceTracker" callExtension [":LOG:PRESENCE:", [[_hash] call CBA_fnc_encodeJSON]];
true;

View File

@@ -0,0 +1,4 @@
#define COMPONENT main
#define COMPONENT_BEAUTIFIED Main
#include "\x\attendancetracker\addons\main\script_mod.hpp"

View File

@@ -1,10 +1,8 @@
#include "script_version.hpp" #include "script_version.hpp"
#define COMPONENT main
#define COMPONENT_BEAUTIFIED Main
#define MAINPREFIX x #define MAINPREFIX x
#define PREFIX AttendanceTracker #define PREFIX attendancetracker
#define PREFIX_BEAUTIFIED AttendanceTracker
#define SUBPREFIX addons #define SUBPREFIX addons
#include "\x\cba\addons\main\script_macros_common.hpp" #include "\x\cba\addons\main\script_macros_common.hpp"

View File

@@ -1,7 +1,7 @@
#define MAJOR 1 #define MAJOR 1
#define MINOR 1 #define MINOR 1
#define PATCH 0 #define PATCH 0
#define BUILD 20231003 #define BUILD 20231012
#define VERSION 1.1 #define VERSION 1.1
#define VERSION_STR MAJOR##.##MINOR##.##PATCH##.##BUILD #define VERSION_STR MAJOR##.##MINOR##.##PATCH##.##BUILD

View File

@@ -13,7 +13,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings"
"time" "time"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
@@ -22,35 +21,25 @@ import (
"github.com/indig0fox/Arma3-AttendanceTracker/internal/util" "github.com/indig0fox/Arma3-AttendanceTracker/internal/util"
"github.com/indig0fox/a3go/a3interface" "github.com/indig0fox/a3go/a3interface"
"github.com/indig0fox/a3go/assemblyfinder" "github.com/indig0fox/a3go/assemblyfinder"
"github.com/rs/zerolog"
) )
const EXTENSION_NAME string = "AttendanceTracker" const EXTENSION_NAME string = "AttendanceTracker"
const ADDON_NAME string = "AttendanceTracker" const ADDON_NAME string = "AttendanceTracker"
const EXTENSION_VERSION string = "dev"
// file paths // file paths
const ATTENDANCE_TABLE string = "attendance" const ATTENDANCE_TABLE string = "attendance"
const MISSIONS_TABLE string = "missions" const MISSIONS_TABLE string = "missions"
const WORLDS_TABLE string = "worlds" const WORLDS_TABLE string = "worlds"
var currentMissionID uint = 0
var RVExtensionChannels = map[string]chan string{
":START:": make(chan string),
":MISSION:HASH:": make(chan string),
":GET:SETTINGS:": make(chan string),
}
var RVExtensionArgsChannels = map[string]chan []string{
":LOG:MISSION:": make(chan []string),
":LOG:PRESENCE:": make(chan []string),
}
var ( var (
EXTENSION_VERSION string = "DEVELOPMENT"
modulePath string modulePath string
modulePathDir string modulePathDir string
initSuccess bool // default false loadedMission *Mission
loadedWorld *World
) )
// configure log output // configure log output
@@ -58,31 +47,28 @@ func init() {
a3interface.SetVersion(EXTENSION_VERSION) a3interface.SetVersion(EXTENSION_VERSION)
a3interface.NewRegistration(":START:"). a3interface.NewRegistration(":START:").
SetDefaultResponse(`["Extension beginning init process"]`).
SetFunction(onStartCommand). SetFunction(onStartCommand).
SetRunInBackground(true). SetRunInBackground(false).
Register() Register()
a3interface.NewRegistration(":MISSION:HASH:"). a3interface.NewRegistration(":MISSION:HASH:").
SetDefaultResponse(`["Retrieving mission hash"]`).
SetFunction(onMissionHashCommand). SetFunction(onMissionHashCommand).
SetRunInBackground(true). SetRunInBackground(false).
Register() Register()
a3interface.NewRegistration(":GET:SETTINGS:"). a3interface.NewRegistration(":GET:SETTINGS:").
SetDefaultResponse(`["Retrieving settings"]`).
SetFunction(onGetSettingsCommand). SetFunction(onGetSettingsCommand).
SetRunInBackground(true). SetRunInBackground(false).
Register() Register()
a3interface.NewRegistration(":LOG:MISSION:"). a3interface.NewRegistration(":LOG:MISSION:").
SetDefaultResponse(`["Logging mission data"]`). SetDefaultResponse(`Logging mission data`).
SetArgsFunction(onLogMissionArgsCommand). SetArgsFunction(onLogMissionArgsCommand).
SetRunInBackground(true). SetRunInBackground(true).
Register() Register()
a3interface.NewRegistration(":LOG:PRESENCE:"). a3interface.NewRegistration(":LOG:PRESENCE:").
SetDefaultResponse(`["Logging presence data"]`). SetDefaultResponse(`Logging presence data`).
SetArgsFunction(onLogPresenceArgsCommand). SetArgsFunction(onLogPresenceArgsCommand).
SetRunInBackground(true). SetRunInBackground(true).
Register() Register()
@@ -91,12 +77,7 @@ func init() {
var err error var err error
modulePath = assemblyfinder.GetModulePath() modulePath = assemblyfinder.GetModulePath()
// get absolute path of module path modulePathDir = filepath.Dir(modulePath)
modulePathAbs, err := filepath.Abs(modulePath)
if err != nil {
panic(err)
}
modulePathDir = filepath.Dir(modulePathAbs)
result, configErr := util.LoadConfig(modulePathDir) result, configErr := util.LoadConfig(modulePathDir)
logger.InitLoggers(&logger.LoggerOptionsType{ logger.InitLoggers(&logger.LoggerOptionsType{
@@ -109,9 +90,11 @@ func init() {
)), )),
AddonName: ADDON_NAME, AddonName: ADDON_NAME,
ExtensionName: EXTENSION_NAME, ExtensionName: EXTENSION_NAME,
ExtensionVersion: EXTENSION_VERSION,
Debug: util.ConfigJSON.GetBool("armaConfig.debug"), Debug: util.ConfigJSON.GetBool("armaConfig.debug"),
Trace: util.ConfigJSON.GetBool("armaConfig.traceLogToFile"), Trace: util.ConfigJSON.GetBool("armaConfig.trace"),
}) })
logger.RotateLogs()
if configErr != nil { if configErr != nil {
logger.Log.Error().Err(configErr).Msgf(`Error loading config`) logger.Log.Error().Err(configErr).Msgf(`Error loading config`)
return return
@@ -119,9 +102,7 @@ func init() {
logger.Log.Info().Msgf(result) logger.Log.Info().Msgf(result)
} }
logger.RotateLogs() logger.Log.Info().Msgf(`%s v%s started`, EXTENSION_NAME, EXTENSION_VERSION)
logger.ArmaOnly.Info().Msgf(`%s v%s started`, EXTENSION_NAME, "0.0.0")
logger.ArmaOnly.Info().Msgf(`Log path: %s`, logger.ActiveOptions.Path) logger.ArmaOnly.Info().Msgf(`Log path: %s`, logger.ActiveOptions.Path)
db.SetConfig(db.ConfigStruct{ db.SetConfig(db.ConfigStruct{
@@ -151,9 +132,10 @@ func init() {
) )
if err != nil { if err != nil {
logger.Log.Error().Err(err).Msgf(`Error migrating database schema`) logger.Log.Error().Err(err).Msgf(`Error migrating database schema`)
} else {
logger.Log.Info().Msgf(`Database schema migrated`)
} }
initSuccess = true
a3interface.WriteArmaCallback( a3interface.WriteArmaCallback(
EXTENSION_NAME, EXTENSION_NAME,
":READY:", ":READY:",
@@ -167,32 +149,22 @@ func onStartCommand(
ctx a3interface.ArmaExtensionContext, ctx a3interface.ArmaExtensionContext,
data string, data string,
) (string, error) { ) (string, error) {
logger.Log.Trace().Msgf(`RVExtension :START: requested`) logger.Log.Debug().Msgf(`RVExtension :START: requested`)
if !initSuccess { loadedWorld = nil
logger.Log.Warn().Msgf(`Received another :START: command before init was complete, ignoring.`) loadedMission = nil
return "Initing!", nil return fmt.Sprintf(
} else { `["%s v%s started"]`,
logger.RotateLogs()
a3interface.WriteArmaCallback(
EXTENSION_NAME, EXTENSION_NAME,
":READY:", EXTENSION_VERSION,
) ), nil
return "Ready!", nil
}
} }
func onMissionHashCommand( func onMissionHashCommand(
ctx a3interface.ArmaExtensionContext, ctx a3interface.ArmaExtensionContext,
data string, data string,
) (string, error) { ) (string, error) {
logger.Log.Trace().Msgf(`RVExtension :MISSION:HASH: requested`) logger.Log.Debug().Msgf(`RVExtension :MISSION:HASH: requested`)
timestamp, hash := getMissionHash() timestamp, hash := getMissionHash()
a3interface.WriteArmaCallback(
EXTENSION_NAME,
":MISSION:HASH:",
timestamp,
hash,
)
return fmt.Sprintf( return fmt.Sprintf(
`[%q, %q]`, `[%q, %q]`,
timestamp, timestamp,
@@ -204,19 +176,14 @@ func onGetSettingsCommand(
ctx a3interface.ArmaExtensionContext, ctx a3interface.ArmaExtensionContext,
data string, data string,
) (string, error) { ) (string, error) {
logger.Log.Trace().Msg(`Settings requested`) logger.Log.Debug().Msg(`RVExtension :GET:SETTINGS: requested`)
armaConfig, err := util.ConfigArmaFormat() // get arma config
if err != nil { c := util.ConfigJSON.Get("armaConfig")
logger.Log.Error().Err(err).Msg(`Error when marshaling arma config`) armaConfig := a3interface.ToArmaHashMap(c)
return "", err return fmt.Sprintf(
} `[%s]`,
logger.Log.Trace().Str("armaConfig", armaConfig).Send()
a3interface.WriteArmaCallback(
EXTENSION_NAME,
":GET:SETTINGS:",
armaConfig, armaConfig,
) ), nil
return armaConfig, nil
} }
func onLogMissionArgsCommand( func onLogMissionArgsCommand(
@@ -224,12 +191,27 @@ func onLogMissionArgsCommand(
command string, command string,
args []string, args []string,
) (string, error) { ) (string, error) {
go func(data []string) { thisLogger := logger.Log.With().Str("command", command).Interface("ctx", ctx).Logger()
writeWorldInfo(data[1]) thisLogger.Debug().Msgf(`RVExtension :LOG:MISSION: requested`)
writeMission(data[0]) var err error
}(args) world, err := writeWorldInfo(args[0], thisLogger)
if err != nil {
return ``, err
}
loadedWorld = &world
return `["Logging mission data"]`, nil mission, err := writeMission(args[1], thisLogger)
if err != nil {
return ``, err
}
loadedMission = &mission
a3interface.WriteArmaCallback(
EXTENSION_NAME,
":LOG:MISSION:SUCCESS:",
)
return ``, nil
} }
func onLogPresenceArgsCommand( func onLogPresenceArgsCommand(
@@ -237,8 +219,10 @@ func onLogPresenceArgsCommand(
command string, command string,
args []string, args []string,
) (string, error) { ) (string, error) {
go writeAttendance(args[0]) thisLogger := logger.Log.With().Str("command", command).Interface("ctx", ctx).Logger()
return `["Logging presence data"]`, nil thisLogger.Debug().Msgf(`RVExtension :LOG:PRESENCE: requested`)
writeAttendance(args[0], thisLogger)
return ``, nil
} }
// getMissionHash will return the current time in UTC and an md5 hash of that time // getMissionHash will return the current time in UTC and an md5 hash of that time
@@ -248,7 +232,7 @@ func getMissionHash() (sqlTime, hashString string) {
nowTime := time.Now().UTC() nowTime := time.Now().UTC()
// mysql format // mysql format
sqlTime = nowTime.Format("2006-01-02 15:04:05") sqlTime = nowTime.Format(time.RFC3339)
hash := md5.Sum([]byte(sqlTime)) hash := md5.Sum([]byte(sqlTime))
hashString = fmt.Sprintf(`%x`, hash) hashString = fmt.Sprintf(`%x`, hash)
@@ -291,86 +275,162 @@ func finalizeUnendedSessions() {
logger.Log.Info().Msgf(`Filled disconnect time of %d events.`, len(events)) logger.Log.Info().Msgf(`Filled disconnect time of %d events.`, len(events))
} }
func writeWorldInfo(worldInfo string) { func writeWorldInfo(worldInfo string, thisLogger zerolog.Logger) (World, error) {
// worldInfo is json, parse it
var wi World parsedInterface, err := a3interface.ParseSQF(worldInfo)
fixedString := unescapeArmaQuotes(worldInfo)
err := json.Unmarshal([]byte(fixedString), &wi)
if err != nil { if err != nil {
logger.Log.Error().Err(err).Msgf(`Error when unmarshalling world info`) thisLogger.Error().Err(err).Msgf(`Error when parsing world info`)
return return World{}, err
}
parsedMap, err := a3interface.ParseSQFHashMap(parsedInterface)
if err != nil {
thisLogger.Error().Err(err).Msgf(`Error when parsing world info`)
return World{}, err
}
thisLogger.Trace().Msgf(`parsedMap: %+v`, parsedMap)
// create world object from map[string]interface{}
var wi = World{}
worldBytes, err := json.Marshal(parsedMap)
if err != nil {
thisLogger.Error().Err(err).Msgf(`Error when marshalling world info`)
return World{}, err
}
err = json.Unmarshal(worldBytes, &wi)
if err != nil {
thisLogger.Error().Err(err).Msgf(`Error when unmarshalling world info`)
return World{}, err
}
thisLogger.Trace().Msgf(`World info: %+v`, wi)
var dbWorld World
db.Client().Where("world_name = ?", wi.WorldName).First(&dbWorld)
// if world exists, use it
if dbWorld.ID > 0 {
thisLogger.Debug().Msgf(`World %s exists with ID %d.`, wi.WorldName, dbWorld.ID)
return dbWorld, nil
} }
// write world if not exist // write world if not exist
var dbWorld World
db.Client().Where("world_name = ?", wi.WorldName).First(&dbWorld)
if dbWorld.ID == 0 {
db.Client().Create(&wi) db.Client().Create(&wi)
if db.Client().Error != nil { if db.Client().Error != nil {
logger.Log.Error().Err(db.Client().Error).Msgf(`Error when creating world`) thisLogger.Error().Err(db.Client().Error).Msgf(`Error when creating world`)
return return World{}, db.Client().Error
}
logger.Log.Info().Msgf(`World %s created.`, wi.WorldName)
} else {
// don't do anything if exists
logger.Log.Debug().Msgf(`World %s exists with ID %d.`, wi.WorldName, dbWorld.ID)
} }
thisLogger.Info().Msgf(`World %s created.`, wi.WorldName)
return wi, nil
} }
func writeMission(missionJSON string) { func writeMission(data string, thisLogger zerolog.Logger) (Mission, error) {
var err error var err error
// writeLog(functionName, fmt.Sprintf(`["%s", "DEBUG"]`, Mission)) parsedInterface, err := a3interface.ParseSQF(data)
// Mission is json, parse it
var mi Mission
fixedString := fixEscapeQuotes(trimQuotes(missionJSON))
err = json.Unmarshal([]byte(fixedString), &mi)
if err != nil { if err != nil {
logger.Log.Error().Err(err).Msgf(`Error when unmarshalling mission`) thisLogger.Error().Err(err).Msgf(`Error when parsing mission info`)
return return Mission{}, err
} }
// get world from WorldName parsedMap, err := a3interface.ParseSQFHashMap(parsedInterface)
var dbWorld World if err != nil {
db.Client().Where("world_name = ?", mi.WorldName).First(&dbWorld) thisLogger.Error().Err(err).Msgf(`Error when parsing mission info`)
if dbWorld.ID == 0 { return Mission{}, err
logger.Log.Error().Msgf(`World %s not found.`, mi.WorldName)
return
} }
mi.WorldID = dbWorld.ID thisLogger.Trace().Msgf(`parsedMap: %+v`, parsedMap)
var mi Mission
// create mission object from map[string]interface{}
missionBytes, err := json.Marshal(parsedMap)
if err != nil {
thisLogger.Error().Err(err).Msgf(`Error when marshalling mission info`)
return Mission{}, err
}
err = json.Unmarshal(missionBytes, &mi)
if err != nil {
thisLogger.Error().Err(err).Msgf(`Error when unmarshalling mission info`)
return Mission{}, err
}
if loadedWorld == nil {
thisLogger.Error().Msgf(`Current world ID not set, cannot create mission`)
return Mission{}, err
}
if loadedWorld.ID == 0 {
thisLogger.Error().Msgf(`Current world ID is 0, cannot create mission`)
return Mission{}, err
}
mi.WorldID = loadedWorld.ID
// write mission to database // write mission to database
db.Client().Create(&mi) db.Client().Create(&mi)
if db.Client().Error != nil { if db.Client().Error != nil {
logger.Log.Error().Err(db.Client().Error).Msgf(`Error when creating mission`) thisLogger.Error().Err(db.Client().Error).Msgf(`Error when creating mission`)
return return Mission{}, db.Client().Error
} }
logger.Log.Info().Msgf(`Mission %s created with ID %d`, mi.MissionName, mi.ID) thisLogger.Info().Msgf(`Mission %s created with ID %d`, mi.MissionName, mi.ID)
currentMissionID = mi.ID
a3interface.WriteArmaCallback(
EXTENSION_NAME,
":LOG:MISSION:SUCCESS:",
"World and mission logged successfully.",
)
return mi, nil
} }
func writeAttendance(data string) { func writeAttendance(data string, thisLogger zerolog.Logger) {
var err error var err error
// data is json, parse it
stringjson := unescapeArmaQuotes(data) parsedInterface, err := a3interface.ParseSQF(data)
var event Session
err = json.Unmarshal([]byte(stringjson), &event)
if err != nil { if err != nil {
logger.Log.Error().Err(err).Msgf(`Error when unmarshalling attendance`) thisLogger.Error().Err(err).Str("data", data).Msgf(`Error when parsing attendance info`)
return return
} }
parsedMap, err := a3interface.ParseSQFHashMap(parsedInterface)
if err != nil {
thisLogger.Error().Err(err).Str("data", data).Msgf(`Error when parsing attendance info`)
return
}
thisLogger.Trace().Msgf(`parsedMap: %+v`, parsedMap)
var thisSession Session
// create session object from map[string]interface{}
sessionBytes, err := json.Marshal(parsedMap)
if err != nil {
thisLogger.Error().Err(err).Str("data", data).Msgf(`Error when marshalling attendance info`)
return
}
err = json.Unmarshal(sessionBytes, &thisSession)
if err != nil {
thisLogger.Error().Err(err).Str("data", data).Msgf(`Error when unmarshalling attendance info`)
return
}
thisLogger2 := thisLogger.With().
Str("playerId", thisSession.PlayerId).
Str("playerUID", thisSession.PlayerUID).
Str("profileName", thisSession.ProfileName).
Logger()
// search existing event // search existing event
var dbEvent Session var dbEvent Session
db.Client(). db.Client().
Where( Where(
"player_id = ? AND mission_hash = ?", "player_id = ? AND mission_hash = ?",
event.PlayerId, thisSession.PlayerId,
event.MissionHash, thisSession.MissionHash,
). ).
Order("join_time_utc desc"). Order("join_time_utc desc").
First(&dbEvent) First(&dbEvent)
if dbEvent.ID != 0 {
if dbEvent.ID > 0 {
// update disconnect time // update disconnect time
dbEvent.DisconnectTimeUTC = sql.NullTime{ dbEvent.DisconnectTimeUTC = sql.NullTime{
Time: time.Now(), Time: time.Now(),
@@ -378,34 +438,32 @@ func writeAttendance(data string) {
} }
err = db.Client().Save(&dbEvent).Error err = db.Client().Save(&dbEvent).Error
if err != nil { if err != nil {
logger.Log.Error().Err(err). thisLogger2.Error().Err(err).
Msgf(`Error when updating disconnect time for event %d`, dbEvent.ID) Msgf(`Error when updating disconnect time for event %d`, dbEvent.ID)
return return
} }
logger.Log.Debug().Msgf(`Attendance updated for %s (%s)`, thisLogger2.Debug().Msgf(`Attendance updated with ID %d`,
dbEvent.ProfileName, dbEvent.ID,
dbEvent.PlayerUID,
) )
} else { } else {
// insert new row // insert new row
event.JoinTimeUTC = sql.NullTime{ thisSession.JoinTimeUTC = sql.NullTime{
Time: time.Now(), Time: time.Now(),
Valid: true, Valid: true,
} }
if currentMissionID == 0 { if loadedMission == nil {
logger.Log.Error().Msgf(`Current mission ID not set, cannot create attendance event`) thisLogger2.Error().Msgf(`Current mission ID not set, cannot create attendance event`)
return return
} }
event.MissionID = currentMissionID thisSession.MissionID = loadedMission.ID
err = db.Client().Create(&event).Error err = db.Client().Create(&thisSession).Error
if err != nil { if err != nil {
logger.Log.Error().Err(err).Msgf(`Error when creating attendance event`) thisLogger2.Error().Err(err).Msgf(`Error when creating attendance event`)
return return
} }
logger.Log.Debug().Msgf(`Attendance created for %s (%s)`, thisLogger2.Info().Msgf(`Attendance created with ID %d`,
event.ProfileName, thisSession.ID,
event.PlayerUID,
) )
} }
} }
@@ -416,20 +474,6 @@ func getTimestamp() string {
return time.Now().Format("2006-01-02 15:04:05") return time.Now().Format("2006-01-02 15:04:05")
} }
func trimQuotes(s string) string {
// trim the start and end quotes from a string
return strings.Trim(s, `"`)
}
func fixEscapeQuotes(s string) string {
// fix the escape quotes in a string
return strings.Replace(s, `""`, `"`, -1)
}
func unescapeArmaQuotes(s string) string {
return fixEscapeQuotes(trimQuotes(s))
}
func main() { func main() {
// loadConfig() // loadConfig()
// fmt.Println("Running DB connect/migrate to build schema...") // fmt.Println("Running DB connect/migrate to build schema...")

View File

@@ -30,9 +30,9 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/sys v0.13.0 // indirect golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect

View File

@@ -213,8 +213,12 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -234,6 +238,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

View File

@@ -52,9 +52,9 @@ func InitLoggers(o *LoggerOptionsType) {
ll = &lumberjack.Logger{ ll = &lumberjack.Logger{
Filename: ActiveOptions.Path, Filename: ActiveOptions.Path,
MaxSize: 5, MaxSize: 5,
MaxBackups: 10, MaxBackups: 8,
MaxAge: 14, MaxAge: 14,
Compress: true, Compress: false,
LocalTime: true, LocalTime: true,
} }
@@ -66,7 +66,7 @@ func InitLoggers(o *LoggerOptionsType) {
armaLogFormatLevel := func(i interface{}) string { armaLogFormatLevel := func(i interface{}) string {
return strings.ToUpper( return strings.ToUpper(
fmt.Sprintf( fmt.Sprintf(
"(%s)", "%s:",
i, i,
)) ))
} }
@@ -117,13 +117,17 @@ func InitLoggers(o *LoggerOptionsType) {
NoColor: true, NoColor: true,
FormatTimestamp: armaLogFormatTimestamp, FormatTimestamp: armaLogFormatTimestamp,
FormatLevel: armaLogFormatLevel, FormatLevel: armaLogFormatLevel,
FieldsExclude: []string{zerolog.CallerFieldName, "ctx"},
}, },
)).With().Timestamp().Logger() )).With().Timestamp().Caller().Logger()
if ActiveOptions.Debug { if ActiveOptions.Debug {
Log = Log.Level(zerolog.DebugLevel) Log = Log.Level(zerolog.DebugLevel)
} else { } else {
Log = Log.Level(zerolog.InfoLevel) Log = Log.Level(zerolog.InfoLevel)
} }
if ActiveOptions.Trace {
Log = Log.Level(zerolog.TraceLevel)
}
} }

3
go.work Normal file
View File

@@ -0,0 +1,3 @@
go 1.20
use ./extension/AttendanceTracker

View File

@@ -0,0 +1,15 @@
version: '3.1'
services:
db:
image: mariadb
restart: always
environment:
- "MARIADB_ROOT_PASSWORD=example"
- "MARIADB_DATABASE=a3attendance"
volumes:
- ./db:/var/lib/mysql
ports:
- "3306:3306"