Skip to content

Commit 5655dab

Browse files
committed
refactor(serverLog): add manager to maintain opened files
Virtual hosts log files can point to same file. Ensure there is only one process writing logs for each file.
1 parent add68d8 commit 5655dab

File tree

6 files changed

+306
-205
lines changed

6 files changed

+306
-205
lines changed

src/app/main.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,33 @@ import (
1414
type App struct {
1515
vhostSvc *goVirtualHost.Service
1616
vhostHandlers []*vhostHandler.VhostHandler
17+
logFileMan *serverLog.FileMan
1718
}
1819

1920
func (app *App) Open() {
2021
errors := app.vhostSvc.Open()
21-
for _, err := range errors {
22-
serverErrHandler.CheckError(err)
23-
}
22+
serverErrHandler.CheckError(errors...)
2423
}
2524

2625
func (app *App) Close() {
27-
for _, vhHandler := range app.vhostHandlers {
28-
vhHandler.Close()
29-
}
30-
3126
app.vhostSvc.Close()
27+
app.logFileMan.Close()
3228
}
3329

3430
func (app *App) ReOpenLog() {
35-
for _, vhhandler := range app.vhostHandlers {
36-
vhhandler.ReOpenLog()
37-
}
31+
errors := app.logFileMan.Reopen()
32+
serverErrHandler.CheckFatal(errors...)
3833
}
3934

4035
func NewApp(params []*param.Param) *App {
4136
vhSvc := goVirtualHost.NewService()
4237
vhHandlers := make([]*vhostHandler.VhostHandler, 0, len(params))
38+
logFileMan := serverLog.NewFileMan()
4339
themes := make(map[string]tpl.Theme)
4440

4541
for _, p := range params {
4642
// logger
47-
logger := serverLog.NewLogger(p.AccessLog, p.ErrorLog)
48-
errors := logger.Open()
43+
logger, errors := logFileMan.NewLogger(p.AccessLog, p.ErrorLog)
4944
serverErrHandler.CheckFatal(errors...)
5045

5146
// ErrHandler
@@ -102,5 +97,6 @@ func NewApp(params []*param.Param) *App {
10297
return &App{
10398
vhostSvc: vhSvc,
10499
vhostHandlers: vhHandlers,
100+
logFileMan: logFileMan,
105101
}
106102
}

src/serverLog/fileMan.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// fileMan maintains opened files shared by multiple logMan
2+
3+
package serverLog
4+
5+
import (
6+
"../util"
7+
"errors"
8+
"fmt"
9+
"os"
10+
"sync"
11+
"time"
12+
)
13+
14+
const chanBuffer = 15
15+
const fileMode = 0660
16+
17+
type fileEntry struct {
18+
fsPath string
19+
info os.FileInfo
20+
file *os.File
21+
ch chan []byte
22+
}
23+
24+
type FileMan struct {
25+
wg *sync.WaitGroup
26+
entries []*fileEntry
27+
}
28+
29+
func getLogEntry(payload []byte) []byte {
30+
buf := make([]byte, 0, len(payload)+21)
31+
32+
buf = append(buf, []byte(util.FormatTimeSecond(time.Now()))...) //19 bytes
33+
buf = append(buf, ' ') // 1 byte
34+
buf = append(buf, payload...)
35+
buf = append(buf, '\n') //1 byte
36+
37+
return buf
38+
}
39+
40+
func openLogFile(fsPath string) (*os.File, error) {
41+
return os.OpenFile(fsPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, fileMode)
42+
}
43+
44+
func newFileEntry(fsPath string, info os.FileInfo, file *os.File) *fileEntry {
45+
ch := make(chan []byte, chanBuffer)
46+
47+
entry := &fileEntry{
48+
fsPath: fsPath,
49+
info: info,
50+
file: file,
51+
ch: ch,
52+
}
53+
54+
return entry
55+
}
56+
57+
func (entry *fileEntry) serve() {
58+
for payload := range entry.ch {
59+
_, e := entry.file.Write(getLogEntry(payload))
60+
if e != nil {
61+
fmt.Fprintln(os.Stderr, e)
62+
}
63+
}
64+
if entry.info != nil { // not Stdout or Stderr
65+
entry.file.Close()
66+
}
67+
}
68+
69+
func (entry *fileEntry) reopen() error {
70+
if entry.info == nil { // Stdout or Stderr
71+
return nil
72+
}
73+
74+
var err error
75+
var info os.FileInfo
76+
var file *os.File
77+
78+
info, err = os.Stat(entry.fsPath)
79+
if os.IsNotExist(err) {
80+
// get file
81+
file, err = openLogFile(entry.fsPath)
82+
if err != nil {
83+
return err
84+
}
85+
// get info
86+
info, err = os.Stat(entry.fsPath)
87+
if err != nil {
88+
return err
89+
}
90+
} else {
91+
if err != nil {
92+
return err
93+
}
94+
95+
if os.SameFile(info, entry.info) {
96+
return nil
97+
}
98+
99+
// use existing info, get file
100+
file, err = openLogFile(entry.fsPath)
101+
if err != nil {
102+
return err
103+
}
104+
}
105+
106+
oldFile := entry.file
107+
entry.info = info
108+
entry.file = file
109+
110+
return oldFile.Close()
111+
}
112+
113+
func (entry *fileEntry) close() {
114+
close(entry.ch)
115+
}
116+
117+
func (fMan *FileMan) getWritingCh(fsPath string, file *os.File) (chan<- []byte, error) {
118+
if len(fsPath) == 0 && file == nil {
119+
return nil, errors.New("log file not provided")
120+
}
121+
122+
var err error
123+
var info os.FileInfo
124+
125+
if file == nil { // regular file
126+
info, err = os.Stat(fsPath)
127+
if os.IsNotExist(err) {
128+
// get file
129+
file, err = openLogFile(fsPath)
130+
if err != nil {
131+
return nil, err
132+
}
133+
// get info
134+
info, err = os.Stat(fsPath)
135+
if err != nil {
136+
return nil, err
137+
}
138+
} else {
139+
if err != nil {
140+
return nil, err
141+
}
142+
143+
for _, entry := range fMan.entries {
144+
if os.SameFile(info, entry.info) {
145+
return entry.ch, nil
146+
}
147+
}
148+
149+
// use existing info, get file
150+
file, err = openLogFile(fsPath)
151+
if err != nil {
152+
return nil, err
153+
}
154+
}
155+
} else { // Stdout or Stderr
156+
for _, entry := range fMan.entries {
157+
if file == entry.file {
158+
return entry.ch, nil
159+
}
160+
}
161+
162+
fsPath = file.Name()
163+
}
164+
165+
entry := newFileEntry(fsPath, info, file)
166+
fMan.entries = append(fMan.entries, entry)
167+
168+
fMan.wg.Add(1)
169+
go func() {
170+
entry.serve()
171+
fMan.wg.Done()
172+
}()
173+
174+
return entry.ch, nil
175+
}
176+
177+
func (fMan *FileMan) Reopen() []error {
178+
var errs []error
179+
180+
for _, entry := range fMan.entries {
181+
err := entry.reopen()
182+
if err != nil {
183+
errs = append(errs, err)
184+
}
185+
}
186+
187+
return errs
188+
}
189+
190+
func (fMan *FileMan) Close() {
191+
for _, entry := range fMan.entries {
192+
entry.close()
193+
}
194+
fMan.wg.Wait()
195+
}
196+
197+
func (fMan *FileMan) NewLogger(accLogFilename, errLogFilename string) (*Logger, []error) {
198+
return newLogger(fMan, accLogFilename, errLogFilename)
199+
}
200+
201+
func NewFileMan() *FileMan {
202+
return &FileMan{
203+
wg: &sync.WaitGroup{},
204+
}
205+
}

0 commit comments

Comments
 (0)