Add silly log level support.
[src/xds/xds-server.git] / main.go
1 // TODO add Doc
2 //
3 package main
4
5 import (
6         "fmt"
7         "os"
8         "os/exec"
9         "os/signal"
10         "path/filepath"
11         "syscall"
12         "time"
13
14         "github.com/Sirupsen/logrus"
15         "github.com/codegangsta/cli"
16         "github.com/iotbzh/xds-server/lib/crosssdk"
17         "github.com/iotbzh/xds-server/lib/folder"
18         "github.com/iotbzh/xds-server/lib/model"
19         "github.com/iotbzh/xds-server/lib/syncthing"
20         "github.com/iotbzh/xds-server/lib/webserver"
21         "github.com/iotbzh/xds-server/lib/xdsconfig"
22 )
23
24 const (
25         appName        = "xds-server"
26         appDescription = "X(cross) Development System Server is a web server that allows to remotely cross build applications."
27         appCopyright   = "Apache-2.0"
28         appUsage       = "X(cross) Development System Server"
29 )
30
31 var appAuthors = []cli.Author{
32         cli.Author{Name: "Sebastien Douheret", Email: "sebastien@iot.bzh"},
33 }
34
35 // AppVersion is the version of this application
36 var AppVersion = "?.?.?"
37
38 // AppSubVersion is the git tag id added to version string
39 // Should be set by compilation -ldflags "-X main.AppSubVersion=xxx"
40 var AppSubVersion = "unknown-dev"
41
42 // Context holds the XDS server context
43 type Context struct {
44         ProgName      string
45         Cli           *cli.Context
46         Config        *xdsconfig.Config
47         Log           *logrus.Logger
48         LogLevelSilly bool
49         SThg          *st.SyncThing
50         SThgCmd       *exec.Cmd
51         SThgInotCmd   *exec.Cmd
52         MFolders      *model.Folders
53         SDKs          *crosssdk.SDKs
54         WWWServer     *webserver.Server
55         Exit          chan os.Signal
56 }
57
58 // NewContext Create a new instance of XDS server
59 func NewContext(cliCtx *cli.Context) *Context {
60         var err error
61
62         // Set logger level and formatter
63         log := cliCtx.App.Metadata["logger"].(*logrus.Logger)
64
65         logLevel := cliCtx.GlobalString("log")
66         if logLevel == "" {
67                 logLevel = "error" // FIXME get from Config DefaultLogLevel
68         }
69         if log.Level, err = logrus.ParseLevel(logLevel); err != nil {
70                 fmt.Printf("Invalid log level : \"%v\"\n", logLevel)
71                 os.Exit(1)
72         }
73         log.Formatter = &logrus.TextFormatter{}
74
75         sillyVal, sillyLog := os.LookupEnv("XDS_LOG_SILLY")
76
77         // Define default configuration
78         ctx := Context{
79                 ProgName:      cliCtx.App.Name,
80                 Cli:           cliCtx,
81                 Log:           log,
82                 LogLevelSilly: (sillyLog && sillyVal == "1"),
83                 Exit:          make(chan os.Signal, 1),
84         }
85
86         // register handler on SIGTERM / exit
87         signal.Notify(ctx.Exit, os.Interrupt, syscall.SIGTERM)
88         go handlerSigTerm(&ctx)
89
90         return &ctx
91 }
92
93 // Handle exit and properly stop/close all stuff
94 func handlerSigTerm(ctx *Context) {
95         <-ctx.Exit
96         if ctx.SThg != nil {
97                 ctx.Log.Infof("Stoping Syncthing... (PID %d)", ctx.SThgCmd.Process.Pid)
98                 ctx.SThg.Stop()
99                 ctx.Log.Infof("Stoping Syncthing-inotify... (PID %d)", ctx.SThgInotCmd.Process.Pid)
100                 ctx.SThg.StopInotify()
101         }
102         if ctx.WWWServer != nil {
103                 ctx.Log.Infof("Stoping Web server...")
104                 ctx.WWWServer.Stop()
105         }
106         os.Exit(0)
107 }
108
109 // Helper function to log message on both stdout and logger
110 func logPrint(ctx *Context, format string, args ...interface{}) {
111         fmt.Printf(format, args...)
112         if ctx.Log.Out != os.Stdout {
113                 ctx.Log.Infof(format, args...)
114         }
115 }
116
117 // XDS Server application main routine
118 func xdsApp(cliCtx *cli.Context) error {
119         var err error
120
121         // Create XDS server context
122         ctx := NewContext(cliCtx)
123
124         // Load config
125         cfg, err := xdsconfig.Init(ctx.Cli, ctx.Log)
126         if err != nil {
127                 return cli.NewExitError(err, -2)
128         }
129         ctx.Config = cfg
130
131         // Logs redirected into a file when logfile option or logsDir config is set
132         ctx.Config.LogVerboseOut = os.Stderr
133         if ctx.Config.FileConf.LogsDir != "" {
134                 if ctx.Config.Options.LogFile != "stdout" {
135                         logFile := ctx.Config.Options.LogFile
136
137                         fdL, err := os.OpenFile(logFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
138                         if err != nil {
139                                 msgErr := fmt.Sprintf("Cannot create log file %s", logFile)
140                                 return cli.NewExitError(msgErr, int(syscall.EPERM))
141                         }
142                         ctx.Log.Out = fdL
143
144                         logPrint(ctx, "Logging file: %s\n", logFile)
145                 }
146
147                 logFileHTTPReq := filepath.Join(ctx.Config.FileConf.LogsDir, "xds-server-verbose.log")
148                 fdLH, err := os.OpenFile(logFileHTTPReq, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
149                 if err != nil {
150                         msgErr := fmt.Sprintf("Cannot create log file %s", logFileHTTPReq)
151                         return cli.NewExitError(msgErr, int(syscall.EPERM))
152                 }
153                 ctx.Config.LogVerboseOut = fdLH
154
155                 logPrint(ctx, "Logging file for HTTP requests:  %s\n", logFileHTTPReq)
156         }
157
158         // Create syncthing instance when section "syncthing" is present in config.json
159         if ctx.Config.FileConf.SThgConf != nil {
160                 ctx.SThg = st.NewSyncThing(ctx.Config, ctx.Log)
161         }
162
163         // Start local instance of Syncthing and Syncthing-notify
164         if ctx.SThg != nil {
165                 ctx.Log.Infof("Starting Syncthing...")
166                 ctx.SThgCmd, err = ctx.SThg.Start()
167                 if err != nil {
168                         return cli.NewExitError(err, -4)
169                 }
170                 logPrint(ctx, "Syncthing started (PID %d)\n", ctx.SThgCmd.Process.Pid)
171
172                 ctx.Log.Infof("Starting Syncthing-inotify...")
173                 ctx.SThgInotCmd, err = ctx.SThg.StartInotify()
174                 if err != nil {
175                         return cli.NewExitError(err, -4)
176                 }
177                 logPrint(ctx, "Syncthing-inotify started (PID %d)\n", ctx.SThgInotCmd.Process.Pid)
178
179                 // Establish connection with local Syncthing (retry if connection fail)
180                 logPrint(ctx, "Establishing connection with Syncthing...\n")
181                 time.Sleep(2 * time.Second)
182                 maxRetry := 30
183                 retry := maxRetry
184                 err = nil
185                 for retry > 0 {
186                         if err = ctx.SThg.Connect(); err == nil {
187                                 break
188                         }
189                         ctx.Log.Warningf("Establishing connection to Syncthing (retry %d/%d)", retry, maxRetry)
190                         time.Sleep(time.Second)
191                         retry--
192                 }
193                 if err != nil || retry == 0 {
194                         return cli.NewExitError(err, -4)
195                 }
196
197                 // FIXME: do we still need Builder notion ? if no cleanup
198                 if ctx.Config.Builder, err = xdsconfig.NewBuilderConfig(ctx.SThg.MyID); err != nil {
199                         return cli.NewExitError(err, -4)
200                 }
201                 ctx.Config.SupportedSharing[folder.TypeCloudSync] = true
202         }
203
204         // Init model folder
205         ctx.MFolders = model.FoldersNew(ctx.Config, ctx.SThg)
206
207         // Load initial folders config from disk
208         if err := ctx.MFolders.LoadConfig(); err != nil {
209                 return cli.NewExitError(err, -5)
210         }
211
212         // Init cross SDKs
213         ctx.SDKs, err = crosssdk.Init(ctx.Config, ctx.Log)
214         if err != nil {
215                 return cli.NewExitError(err, -6)
216         }
217
218         // Create Web Server
219         ctx.WWWServer = webserver.New(ctx.Config, ctx.MFolders, ctx.SDKs, ctx.Log, ctx.LogLevelSilly)
220
221         // Run Web Server until exit requested (blocking call)
222         if err = ctx.WWWServer.Serve(); err != nil {
223                 ctx.Log.Println(err)
224                 return cli.NewExitError(err, -7)
225         }
226
227         return cli.NewExitError("Program exited ", -99)
228 }
229
230 // main
231 func main() {
232
233         // Create a new instance of the logger
234         log := logrus.New()
235
236         // Create a new App instance
237         app := cli.NewApp()
238         app.Name = appName
239         app.Description = appDescription
240         app.Usage = appUsage
241         app.Version = AppVersion + " (" + AppSubVersion + ")"
242         app.Authors = appAuthors
243         app.Copyright = appCopyright
244         app.Metadata = make(map[string]interface{})
245         app.Metadata["version"] = AppVersion
246         app.Metadata["git-tag"] = AppSubVersion
247         app.Metadata["logger"] = log
248
249         app.Flags = []cli.Flag{
250                 cli.StringFlag{
251                         Name:   "config, c",
252                         Usage:  "JSON config file to use\n\t",
253                         EnvVar: "APP_CONFIG",
254                 },
255                 cli.StringFlag{
256                         Name:   "log, l",
257                         Value:  "error",
258                         Usage:  "logging level (supported levels: panic, fatal, error, warn, info, debug)\n\t",
259                         EnvVar: "LOG_LEVEL",
260                 },
261                 cli.StringFlag{
262                         Name:   "logfile",
263                         Value:  "stdout",
264                         Usage:  "filename where logs will be redirected (default stdout)\n\t",
265                         EnvVar: "LOG_FILENAME",
266                 },
267                 cli.BoolFlag{
268                         Name:   "no-folderconfig, nfc",
269                         Usage:  fmt.Sprintf("Do not read folder config file (%s)\n\t", xdsconfig.FoldersConfigFilename),
270                         EnvVar: "NO_FOLDERCONFIG",
271                 },
272         }
273
274         // only one action: Web Server
275         app.Action = xdsApp
276
277         app.Run(os.Args)
278 }