Feature/service logs directory (#1046)
This commit is contained in:
commit
1a01c9bcb9
|
|
@ -117,6 +117,7 @@ All configuration options are covered in the solution sections
|
|||
| `WebUIDir` | The directory to serve the web UI from. | Calculated at runtime. | Requires Restart | -
|
||||
| `CronSupportForSeconds` | Whether or not to support seconds in cron expressions. | `false` | Requires Restart | xref:action_execution/oncron.adoc[Cron]
|
||||
| `SaveLogs` | Whether or not to save logs to disk. | `[]` | Requires Restart | xref:action_customization/savelogs.adoc[Save Logs]
|
||||
| `ServiceLogs` | Windows process log directory (`serviceLogs.directory`). | `%ProgramData%\OliveTin\logs\` on Windows | Requires Restart | xref:install/windows_service.adoc#windows-service-logs[Windows service logs]
|
||||
| `Prometheus` | Prometheus configuration. | `-` | Requires Restart | xref:advanced_configuration/prometheus.adoc[Prometheus]
|
||||
|===
|
||||
|
||||
|
|
|
|||
|
|
@ -9,4 +9,6 @@ You can instal install OliveTin as a Windows service, follow the instructions to
|
|||
1. You can download the latest version of OliveTin here: link:https://github.com/OliveTin/OliveTin/releases/latest/download/OliveTin-windows-amd64.zip[`OliveTin-windows-amd64.zip`]
|
||||
2. Unzip and run "OliveTin.exe"
|
||||
|
||||
include::partial$install/windows_service_logs.adoc[]
|
||||
|
||||
include::partial$install/post_generic.adoc[]
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ You can download the latest version of OliveTin here: link:https://github.com/Ol
|
|||
* Create c:/ProgramData/OliveTin/
|
||||
** Copy the **config.yaml** file into this directory.
|
||||
|
||||
include::partial$install/windows_service_logs.adoc[]
|
||||
|
||||
== Test OliveTin startup
|
||||
|
||||
Open a command prompt and make suer you are in the c:/Program Files/OliveTin/ directory, then run:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,27 @@ How you read logs depends on how OliveTin is installed:
|
|||
* **Container (Docker / Podman):** logs are what the container runtime captured from OliveTin’s standard output.
|
||||
* **systemd service:** logs are stored by the system journal for the `OliveTin` unit.
|
||||
* **Manual / binary** (for example `./OliveTin` in a terminal): messages print to that terminal—copy them from there, or redirect output to a file when testing.
|
||||
* **Windows (binary or service):** OliveTin writes process logs to a file under `%ProgramData%\OliveTin\logs\` by default. You can override this with xref:install/windows_service.adoc#windows-service-logs[`serviceLogs.directory`] in `config.yaml`.
|
||||
|
||||
[#windows]
|
||||
== Windows (binary or service)
|
||||
|
||||
By default, OliveTin on Windows writes process logs to:
|
||||
|
||||
[source]
|
||||
----
|
||||
%ProgramData%\OliveTin\logs\OliveTin-service-<timestamp>.log
|
||||
----
|
||||
|
||||
To use a custom directory (for portable installs), set in `config.yaml`:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
serviceLogs:
|
||||
directory: C:\Path\To\Logs\
|
||||
----
|
||||
|
||||
See xref:install/windows_service.adoc#windows-service-logs[Windows service logs directory] for details. This setting is ignored on non-Windows platforms.
|
||||
|
||||
[#manual-binary]
|
||||
== Manual or binary run
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
[#windows-service-logs]
|
||||
== Process log directory
|
||||
|
||||
On Windows, OliveTin writes its **process logs** (startup messages, configuration load, errors, and internal diagnostics) to a log file on disk. By default these files are stored under `%ProgramData%\OliveTin\logs\` as `OliveTin-service-<timestamp>.log`.
|
||||
|
||||
This is separate from xref:action_customization/savelogs.adoc[action execution logs] (`saveLogs`), which persist command output from individual actions.
|
||||
|
||||
Portable or self-contained installs often keep OliveTin, its configuration, and its logs together in one folder. Without a custom path, process logs always go to `%ProgramData%`, even when you run OliveTin from another location.
|
||||
|
||||
Add a `serviceLogs` block to your `config.yaml`:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
serviceLogs:
|
||||
directory: ./logs/service/
|
||||
----
|
||||
|
||||
OliveTin creates the directory if it does not exist and writes a new timestamped log file there on each startup.
|
||||
|
||||
If `serviceLogs.directory` is omitted, OliveTin uses the default location: `%ProgramData%\OliveTin\logs\`.
|
||||
|
||||
Relative paths (for example `./logs/service/`) are resolved from the directory containing `OliveTin.exe`. This keeps portable installs self-contained when you colocate logs with the application.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`serviceLogs.directory` is **Windows only**. If you set it on Linux, macOS, or in a container, OliveTin logs an error at startup and ignores the setting. On those platforms, use xref:troubleshooting/service-logs.adoc[service logs troubleshooting] for how to read process output (for example `journalctl` or container logs).
|
||||
====
|
||||
|
|
@ -176,6 +176,7 @@ type Config struct {
|
|||
Prometheus PrometheusConfig `koanf:"prometheus"`
|
||||
Security SecurityConfig `koanf:"security"`
|
||||
SaveLogs SaveLogsConfig `koanf:"saveLogs"`
|
||||
ServiceLogs ServiceLogsConfig `koanf:"serviceLogs"`
|
||||
DefaultIconForActions string `koanf:"defaultIconForActions"`
|
||||
DefaultIconForDirectories string `koanf:"defaultIconForDirectories"`
|
||||
DefaultIconForBack string `koanf:"defaultIconForBack"`
|
||||
|
|
@ -230,6 +231,10 @@ type SaveLogsConfig struct {
|
|||
OutputDirectory string `koanf:"outputDirectory"`
|
||||
}
|
||||
|
||||
type ServiceLogsConfig struct {
|
||||
Directory string `koanf:"directory"`
|
||||
}
|
||||
|
||||
type LogDebugOptions struct {
|
||||
SingleFrontendRequests bool `koanf:"singleFrontendRequests"`
|
||||
SingleFrontendRequestHeaders bool `koanf:"singleFrontendRequestHeaders"`
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ func (cfg *Config) Sanitize() {
|
|||
cfg.sanitizeLogHistoryPageSize()
|
||||
cfg.sanitizeLocalUsers()
|
||||
cfg.sanitizeSecurityHeaders()
|
||||
cfg.sanitizeServiceLogs()
|
||||
|
||||
// log.Infof("cfg %p", cfg)
|
||||
|
||||
|
|
@ -168,6 +170,16 @@ func (cfg *Config) sanitizeLogLevel() {
|
|||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) sanitizeServiceLogs() {
|
||||
if cfg.ServiceLogs.Directory == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
log.Errorf("serviceLogs.directory is configured but this option is only supported on Windows")
|
||||
}
|
||||
}
|
||||
|
||||
func (action *Action) sanitize(cfg *Config) {
|
||||
if action.Timeout < 3 {
|
||||
action.Timeout = 3
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -160,3 +163,22 @@ func TestValidateUniqueLocalUserAPIKeys(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSanitizeServiceLogsUnsupportedPlatform(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("serviceLogs.directory platform check only applies on non-Windows")
|
||||
}
|
||||
|
||||
var logBuffer bytes.Buffer
|
||||
previousOutput := logrus.StandardLogger().Out
|
||||
logrus.SetOutput(&logBuffer)
|
||||
t.Cleanup(func() {
|
||||
logrus.SetOutput(previousOutput)
|
||||
})
|
||||
|
||||
cfg := DefaultConfig()
|
||||
cfg.ServiceLogs.Directory = "/var/log/OliveTin"
|
||||
cfg.Sanitize()
|
||||
|
||||
assert.Contains(t, logBuffer.String(), "serviceLogs.directory is configured but this option is only supported on Windows")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package servicehost
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func resolveLogDirectory(dir string, baseDir string) string {
|
||||
if dir == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if filepath.IsAbs(dir) {
|
||||
return dir
|
||||
}
|
||||
|
||||
if baseDir == "" {
|
||||
return dir
|
||||
}
|
||||
|
||||
return filepath.Join(baseDir, dir)
|
||||
}
|
||||
|
||||
func executableDirectory() (string, error) {
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Dir(ex), nil
|
||||
}
|
||||
|
||||
func configuredServiceLogDirectory(dir string) (string, error) {
|
||||
if dir == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
exeDir, err := executableDirectory()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resolveLogDirectory(dir, exeDir), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package servicehost
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestResolveLogDirectory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
baseDir := filepath.Join(t.TempDir(), "OliveTin")
|
||||
absoluteDir := t.TempDir()
|
||||
|
||||
assert.Equal(t, "", resolveLogDirectory("", baseDir))
|
||||
assert.Equal(t, absoluteDir, resolveLogDirectory(absoluteDir, baseDir))
|
||||
assert.Equal(t, filepath.Join(baseDir, "logs", "service"), resolveLogDirectory("./logs/service", baseDir))
|
||||
assert.Equal(t, "logs/service", resolveLogDirectory("logs/service", ""))
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Start(mode string) {
|
||||
func Start(_ string, _ string) {
|
||||
log.Debugf("servicehost nonwin")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,9 +50,20 @@ func (m *otWindowsService) Execute(args []string, r <-chan svc.ChangeRequest, st
|
|||
}
|
||||
}
|
||||
|
||||
func setupLogging() {
|
||||
logsDir := path.Join(GetConfigFilePath(), "logs")
|
||||
func setupLogging(serviceLogDirectory string) {
|
||||
logsDir, err := configuredServiceLogDirectory(serviceLogDirectory)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to resolve serviceLogs.directory relative to executable: %v", err)
|
||||
}
|
||||
|
||||
if logsDir == "" {
|
||||
logsDir = path.Join(GetConfigFilePath(), "logs")
|
||||
}
|
||||
|
||||
openServiceLogFile(logsDir)
|
||||
}
|
||||
|
||||
func openServiceLogFile(logsDir string) {
|
||||
os.MkdirAll(logsDir, 0755)
|
||||
|
||||
timestamp := time.Now().Format("2006-01-02_15-04-05")
|
||||
|
|
@ -65,11 +76,12 @@ func setupLogging() {
|
|||
|
||||
if err != nil {
|
||||
log.Infof("Failed to open log file: %v", err)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("Switching to log file: %v", f.Name())
|
||||
log.SetOutput(f)
|
||||
log.Infof("Opened log file: %v", f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func GetConfigFilePath() string {
|
||||
|
|
@ -132,8 +144,8 @@ func startServiceHandler(mode string) {
|
|||
|
||||
}
|
||||
|
||||
func Start(mode string) {
|
||||
setupLogging()
|
||||
func Start(mode string, serviceLogDirectory string) {
|
||||
setupLogging(serviceLogDirectory)
|
||||
|
||||
go startServiceHandler(mode)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ func warnIfPuidGuid() {
|
|||
}
|
||||
|
||||
func main() {
|
||||
servicehost.Start(cfg.ServiceHostMode)
|
||||
servicehost.Start(cfg.ServiceHostMode, cfg.ServiceLogs.Directory)
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"configDir": cfg.GetDir(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue