144 lines
3.7 KiB
Go
144 lines
3.7 KiB
Go
// Package vlog is the verbose debug logger used by the CLI, the print helper
|
|
// and the IPP/watch servers. On Windows logs go to %ProgramData%\Verae\logs
|
|
// (shared, ACL'd by the installer so Users can write); elsewhere to the user
|
|
// cache dir. Every print job carries a job ID so one job can be followed from
|
|
// IPP receive → save → hash → API → certificate.
|
|
package vlog
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
mu sync.Mutex
|
|
out io.Writer = os.Stderr
|
|
file *os.File
|
|
comp = "verae"
|
|
jobSeq uint64
|
|
debugOn = os.Getenv("VERAE_DEBUG") == "1"
|
|
)
|
|
|
|
// Dir returns the log directory for this platform.
|
|
func Dir() string {
|
|
if runtime.GOOS == "windows" {
|
|
pd := os.Getenv("ProgramData")
|
|
if pd == "" {
|
|
pd = `C:\ProgramData`
|
|
}
|
|
return filepath.Join(pd, "Verae", "logs")
|
|
}
|
|
if d, err := os.UserCacheDir(); err == nil {
|
|
return filepath.Join(d, "Verae", "logs")
|
|
}
|
|
return filepath.Join(os.TempDir(), "verae-logs")
|
|
}
|
|
|
|
func userTag() string {
|
|
if u, err := user.Current(); err == nil {
|
|
n := u.Username
|
|
if i := strings.LastIndexAny(n, `\/`); i >= 0 {
|
|
n = n[i+1:]
|
|
}
|
|
return sanitize(n)
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
func sanitize(s string) string {
|
|
var b strings.Builder
|
|
for _, r := range s {
|
|
if r < 128 && (r == '-' || r == '_' || r == '.' || (r >= '0' && r <= '9') || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')) {
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
if b.Len() == 0 {
|
|
return "x"
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// Init opens <Dir>/<component>-<user>.log (append). alsoStderr tees output.
|
|
// Falls back to %TEMP% if the shared dir is not writable. Returns the path.
|
|
func Init(component string, alsoStderr bool) string {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
comp = component
|
|
name := fmt.Sprintf("%s-%s.log", component, userTag())
|
|
var path string
|
|
for _, d := range []string{Dir(), filepath.Join(os.TempDir(), "Verae", "logs")} {
|
|
if err := os.MkdirAll(d, 0o775); err != nil {
|
|
continue
|
|
}
|
|
p := filepath.Join(d, name)
|
|
f, err := os.OpenFile(p, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o664)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
file, path = f, p
|
|
break
|
|
}
|
|
switch {
|
|
case file != nil && alsoStderr:
|
|
out = io.MultiWriter(file, os.Stderr)
|
|
case file != nil:
|
|
out = file
|
|
default:
|
|
out = os.Stderr
|
|
}
|
|
return path
|
|
}
|
|
|
|
// SetDebug enables verbose (Debugf) output.
|
|
func SetDebug(on bool) { debugOn = on || debugOn }
|
|
|
|
// DebugEnabled reports whether verbose output is on.
|
|
func DebugEnabled() bool { return debugOn }
|
|
|
|
func write(level, job, msg string) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
ts := time.Now().Format("2006-01-02T15:04:05.000-07:00")
|
|
if job == "" {
|
|
job = "-"
|
|
}
|
|
fmt.Fprintf(out, "%s %s %s pid=%d job=%s %s\n", ts, level, comp, os.Getpid(), job, strings.TrimRight(msg, "\n"))
|
|
}
|
|
|
|
// Printf logs at INFO with no job.
|
|
func Printf(format string, a ...any) { write("INFO", "", fmt.Sprintf(format, a...)) }
|
|
|
|
// Errorf logs at ERROR with no job.
|
|
func Errorf(format string, a ...any) { write("ERROR", "", fmt.Sprintf(format, a...)) }
|
|
|
|
// Debugf logs at DEBUG when enabled.
|
|
func Debugf(format string, a ...any) {
|
|
if debugOn {
|
|
write("DEBUG", "", fmt.Sprintf(format, a...))
|
|
}
|
|
}
|
|
|
|
// NewJobID returns J<yyyymmdd-hhmmss>-<src>-<n>.
|
|
func NewJobID(src string) string {
|
|
n := atomic.AddUint64(&jobSeq, 1)
|
|
return fmt.Sprintf("J%s-%s-%d", time.Now().Format("20060102-150405"), sanitize(src), n)
|
|
}
|
|
|
|
// Job is a logger bound to a job ID.
|
|
type Job string
|
|
|
|
func (j Job) Printf(format string, a ...any) { write("INFO", string(j), fmt.Sprintf(format, a...)) }
|
|
func (j Job) Errorf(format string, a ...any) { write("ERROR", string(j), fmt.Sprintf(format, a...)) }
|
|
func (j Job) Debugf(format string, a ...any) {
|
|
if debugOn {
|
|
write("DEBUG", string(j), fmt.Sprintf(format, a...))
|
|
}
|
|
}
|