fix: configurable log directory for windows
9;133u 0;133u
This commit is contained in:
commit
9d64b64bf9
|
|
@ -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 | -
|
| `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]
|
| `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]
|
| `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]
|
| `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`]
|
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"
|
2. Unzip and run "OliveTin.exe"
|
||||||
|
|
||||||
|
include::partial$install/windows_service_logs.adoc[]
|
||||||
|
|
||||||
include::partial$install/post_generic.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/
|
* Create c:/ProgramData/OliveTin/
|
||||||
** Copy the **config.yaml** file into this directory.
|
** Copy the **config.yaml** file into this directory.
|
||||||
|
|
||||||
|
include::partial$install/windows_service_logs.adoc[]
|
||||||
|
|
||||||
== Test OliveTin startup
|
== Test OliveTin startup
|
||||||
|
|
||||||
Open a command prompt and make suer you are in the c:/Program Files/OliveTin/ directory, then run:
|
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.
|
* **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.
|
* **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.
|
* **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-binary]
|
||||||
== Manual or binary run
|
== 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"`
|
Prometheus PrometheusConfig `koanf:"prometheus"`
|
||||||
Security SecurityConfig `koanf:"security"`
|
Security SecurityConfig `koanf:"security"`
|
||||||
SaveLogs SaveLogsConfig `koanf:"saveLogs"`
|
SaveLogs SaveLogsConfig `koanf:"saveLogs"`
|
||||||
|
ServiceLogs ServiceLogsConfig `koanf:"serviceLogs"`
|
||||||
DefaultIconForActions string `koanf:"defaultIconForActions"`
|
DefaultIconForActions string `koanf:"defaultIconForActions"`
|
||||||
DefaultIconForDirectories string `koanf:"defaultIconForDirectories"`
|
DefaultIconForDirectories string `koanf:"defaultIconForDirectories"`
|
||||||
DefaultIconForBack string `koanf:"defaultIconForBack"`
|
DefaultIconForBack string `koanf:"defaultIconForBack"`
|
||||||
|
|
@ -230,6 +231,10 @@ type SaveLogsConfig struct {
|
||||||
OutputDirectory string `koanf:"outputDirectory"`
|
OutputDirectory string `koanf:"outputDirectory"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServiceLogsConfig struct {
|
||||||
|
Directory string `koanf:"directory"`
|
||||||
|
}
|
||||||
|
|
||||||
type LogDebugOptions struct {
|
type LogDebugOptions struct {
|
||||||
SingleFrontendRequests bool `koanf:"singleFrontendRequests"`
|
SingleFrontendRequests bool `koanf:"singleFrontendRequests"`
|
||||||
SingleFrontendRequestHeaders bool `koanf:"singleFrontendRequestHeaders"`
|
SingleFrontendRequestHeaders bool `koanf:"singleFrontendRequestHeaders"`
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
|
@ -18,6 +19,7 @@ func (cfg *Config) Sanitize() {
|
||||||
cfg.sanitizeLogHistoryPageSize()
|
cfg.sanitizeLogHistoryPageSize()
|
||||||
cfg.sanitizeLocalUsers()
|
cfg.sanitizeLocalUsers()
|
||||||
cfg.sanitizeSecurityHeaders()
|
cfg.sanitizeSecurityHeaders()
|
||||||
|
cfg.sanitizeServiceLogs()
|
||||||
|
|
||||||
// log.Infof("cfg %p", cfg)
|
// 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) {
|
func (action *Action) sanitize(cfg *Config) {
|
||||||
if action.Timeout < 3 {
|
if action.Timeout < 3 {
|
||||||
action.Timeout = 3
|
action.Timeout = 3
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
@ -160,3 +163,22 @@ func TestValidateUniqueLocalUserAPIKeys(t *testing.T) {
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
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"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start(mode string) {
|
func Start(_ string, _ string) {
|
||||||
log.Debugf("servicehost nonwin")
|
log.Debugf("servicehost nonwin")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,20 @@ func (m *otWindowsService) Execute(args []string, r <-chan svc.ChangeRequest, st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupLogging() {
|
func setupLogging(serviceLogDirectory string) {
|
||||||
logsDir := path.Join(GetConfigFilePath(), "logs")
|
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) {
|
||||||
if err := os.MkdirAll(logsDir, 0755); err != nil {
|
if err := os.MkdirAll(logsDir, 0755); err != nil {
|
||||||
log.Errorf("Failed to create logs directory %v: %v", logsDir, err)
|
log.Errorf("Failed to create logs directory %v: %v", logsDir, err)
|
||||||
return
|
return
|
||||||
|
|
@ -68,11 +79,12 @@ func setupLogging() {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to open log file: %v", err)
|
log.Errorf("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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("Switching to log file: %v", f.Name())
|
||||||
|
log.SetOutput(f)
|
||||||
|
log.Infof("Opened log file: %v", f.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfigFilePath() string {
|
func GetConfigFilePath() string {
|
||||||
|
|
@ -135,8 +147,8 @@ func startServiceHandler(mode string) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(mode string) {
|
func Start(mode string, serviceLogDirectory string) {
|
||||||
setupLogging()
|
setupLogging(serviceLogDirectory)
|
||||||
|
|
||||||
go startServiceHandler(mode)
|
go startServiceHandler(mode)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ func warnIfPuidGuid() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
servicehost.Start(cfg.ServiceHostMode)
|
servicehost.Start(cfg.ServiceHostMode, cfg.ServiceLogs.Directory)
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"configDir": cfg.GetDir(),
|
"configDir": cfg.GetDir(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue