Skip to content

Commit c67013d

Browse files
committed
feat(run): Write logs to <stackdir>/.nitric/run.log
This starts up a little syslog server and writes the logs to file.
1 parent 85d633a commit c67013d

File tree

12 files changed

+217
-9
lines changed

12 files changed

+217
-9
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ require (
3838
golang.org/x/net v0.0.0-20211105192438-b53810dc28af // indirect
3939
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect
4040
google.golang.org/grpc v1.41.0
41+
gopkg.in/mcuadros/go-syslog.v2 v2.3.0
4142
gopkg.in/yaml.v2 v2.4.0
4243
)
4344

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2454,6 +2454,8 @@ gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
24542454
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
24552455
gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
24562456
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
2457+
gopkg.in/mcuadros/go-syslog.v2 v2.3.0 h1:kcsiS+WsTKyIEPABJBJtoG0KkOS6yzvJ+/eZlhD79kk=
2458+
gopkg.in/mcuadros/go-syslog.v2 v2.3.0/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U=
24572459
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
24582460
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
24592461
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=

mocks/mock_containerengine/mock_containerengine.go

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/build/build_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func TestCreateBaseDev(t *testing.T) {
3939

4040
me.EXPECT().Build(gomock.Any(), dir, "nitric-ts-dev", map[string]string{})
4141

42-
containerengine.MockEngine = me
42+
containerengine.DiscoveredEngine = me
4343

4444
if err := CreateBaseDev(dir, map[string]string{"ts": "nitric-ts-dev"}); err != nil {
4545
t.Errorf("CreateBaseDev() error = %v", err)

pkg/cmd/run/root.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/spf13/cobra"
3030

3131
"github.com/nitrictech/newcli/pkg/build"
32+
"github.com/nitrictech/newcli/pkg/containerengine"
3233
"github.com/nitrictech/newcli/pkg/run"
3334
"github.com/nitrictech/newcli/pkg/stack"
3435
"github.com/nitrictech/newcli/pkg/tasklet"
@@ -56,6 +57,12 @@ var runCmd = &cobra.Command{
5657
cobra.CheckErr(err)
5758
}
5859

60+
ce, err := containerengine.Discover()
61+
cobra.CheckErr(err)
62+
63+
logger := ce.Logger(contextDir)
64+
cobra.CheckErr(logger.Start())
65+
5966
createBaseImage := tasklet.Runner{
6067
StartMsg: "Creating Dev Image",
6168
Runner: func(tCtx tasklet.TaskletContext) error {
@@ -80,9 +87,18 @@ var runCmd = &cobra.Command{
8087
}(memerr)
8188

8289
for {
90+
select {
91+
case err := <-memerr:
92+
// catch any early errors from Start()
93+
if err != nil {
94+
return err
95+
}
96+
default:
97+
}
8398
if ls.Running() {
8499
break
85100
}
101+
tCtx.Spinner().UpdateText("Waiting for Local Services to be ready")
86102
time.Sleep(time.Second)
87103
}
88104
return nil
@@ -127,6 +143,7 @@ var runCmd = &cobra.Command{
127143
}
128144
}
129145

146+
_ = logger.Stop()
130147
// Stop the membrane
131148
cobra.CheckErr(ls.Stop())
132149
},

pkg/cmd/stack/root.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ var stackDescribeCmd = &cobra.Command{
123123
Long: `Describes stack dependencies`,
124124
Run: func(cmd *cobra.Command, args []string) {
125125
contextDir := stack.StackPath()
126-
127126
cc, err := codeconfig.New(contextDir, args[0])
128127
cobra.CheckErr(err)
129128

pkg/containerengine/docker.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import (
2222
"encoding/json"
2323
"fmt"
2424
"io"
25+
"log"
2526
"os/exec"
27+
"path"
2628
"strings"
2729
"time"
2830

@@ -40,7 +42,8 @@ import (
4042
)
4143

4244
type docker struct {
43-
cli *client.Client
45+
cli *client.Client
46+
syslog *localSyslog
4447
}
4548

4649
var _ ContainerEngine = &docker{}
@@ -67,6 +70,10 @@ func newDocker() (ContainerEngine, error) {
6770
return &docker{cli: cli}, err
6871
}
6972

73+
func (d *docker) Type() string {
74+
return "docker"
75+
}
76+
7077
func tarContextDir(relDockerfile, contextDir string) (io.ReadCloser, error) {
7178
excludes, err := build.ReadDockerignore(contextDir)
7279
if err != nil {
@@ -137,7 +144,7 @@ func print(rd io.Reader) error {
137144
return err
138145
}
139146
if len(line.Stream) > 0 {
140-
fmt.Print(line.Stream)
147+
log.Default().Print(line.Stream)
141148
}
142149
}
143150

@@ -278,3 +285,12 @@ func (d *docker) ContainerExec(containerName string, cmd []string, workingDir st
278285
return fmt.Errorf("%s %v exited with %d", containerName, cmd, res.ExitCode)
279286
}
280287
}
288+
289+
func (d *docker) Logger(stackPath string) ContainerLogger {
290+
if d.syslog == nil {
291+
d.syslog = &localSyslog{
292+
logPath: path.Join(utils.NitricLogDir(stackPath), "run.log"),
293+
}
294+
}
295+
return d.syslog
296+
}

pkg/containerengine/podman.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"io"
2424
"os/exec"
25+
"path"
2526
"strings"
2627
"time"
2728

@@ -30,6 +31,8 @@ import (
3031
"github.com/docker/docker/api/types/network"
3132
"github.com/docker/docker/client"
3233
"github.com/pkg/errors"
34+
35+
"github.com/nitrictech/newcli/pkg/utils"
3336
)
3437

3538
// use docker client to podman socket.
@@ -75,6 +78,10 @@ func newPodman() (ContainerEngine, error) {
7578
return &podman{docker: &docker{cli: cli}}, err
7679
}
7780

81+
func (p *podman) Type() string {
82+
return "podman"
83+
}
84+
7885
func (p *podman) Build(dockerfile, path, imageTag string, buildArgs map[string]string) error {
7986
return p.docker.Build(dockerfile, path, imageTag, buildArgs)
8087
}
@@ -122,3 +129,23 @@ func (p *podman) RemoveByLabel(name, value string) error {
122129
func (p *podman) ContainerExec(containerName string, cmd []string, workingDir string) error {
123130
return p.docker.ContainerExec(containerName, cmd, workingDir)
124131
}
132+
133+
func (p *podman) Logger(stackPath string) ContainerLogger {
134+
return &jsonfile{logPath: path.Join(utils.NitricLogDir(stackPath), "run.log")}
135+
}
136+
137+
type jsonfile struct {
138+
logPath string
139+
}
140+
141+
func (j *jsonfile) Config() *container.LogConfig {
142+
return &container.LogConfig{
143+
Type: "json-file",
144+
Config: map[string]string{
145+
"path": j.logPath,
146+
},
147+
}
148+
}
149+
150+
func (j *jsonfile) Stop() error { return nil }
151+
func (j *jsonfile) Start() error { return nil }

pkg/containerengine/syslog.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright Nitric Pty Ltd.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at:
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package containerengine
18+
19+
import (
20+
"fmt"
21+
"log"
22+
slog "log/syslog"
23+
"net"
24+
"os"
25+
"path"
26+
27+
"github.com/docker/docker/api/types/container"
28+
"github.com/hashicorp/consul/sdk/freeport"
29+
"gopkg.in/mcuadros/go-syslog.v2"
30+
31+
"github.com/nitrictech/newcli/pkg/utils"
32+
)
33+
34+
type localSyslog struct {
35+
logPath string
36+
file *os.File
37+
port int
38+
server *syslog.Server
39+
}
40+
41+
func (s *localSyslog) Stop() error {
42+
errList := utils.NewErrorList()
43+
44+
errList.Add(s.server.Kill())
45+
s.server.Wait()
46+
errList.Add(s.file.Close())
47+
48+
return errList.Aggregate()
49+
}
50+
51+
func (s *localSyslog) Config() *container.LogConfig {
52+
return &container.LogConfig{
53+
Type: "syslog",
54+
Config: map[string]string{
55+
"syslog-address": "udp://" + net.JoinHostPort("localhost", fmt.Sprint(s.port)),
56+
"tag": "{{.ImageName}}/{{.Name}}/{{.ID}}",
57+
},
58+
}
59+
}
60+
61+
func (s *localSyslog) Start() error {
62+
ports, err := freeport.Take(1)
63+
if err != nil {
64+
return err
65+
}
66+
s.port = ports[0]
67+
68+
channel := make(syslog.LogPartsChannel)
69+
handler := syslog.NewChannelHandler(channel)
70+
71+
s.server = syslog.NewServer()
72+
s.server.SetFormat(syslog.Automatic)
73+
s.server.SetHandler(handler)
74+
err = s.server.ListenUDP(net.JoinHostPort("0.0.0.0", fmt.Sprint(s.port)))
75+
if err != nil {
76+
return err
77+
}
78+
79+
err = s.server.Boot()
80+
if err != nil {
81+
return err
82+
}
83+
84+
err = os.MkdirAll(path.Dir(s.logPath), 0777)
85+
if err != nil {
86+
return err
87+
}
88+
89+
s.file, err = os.OpenFile(s.logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
90+
if err != nil {
91+
return err
92+
}
93+
94+
go func(channel syslog.LogPartsChannel) {
95+
for logParts := range channel {
96+
fmt.Fprintf(s.file, "%s %s %s\n", logParts["timestamp"], logParts["tag"], logParts["content"])
97+
}
98+
}(channel)
99+
100+
// set up the log client
101+
logwriter, err := slog.Dial("udp", net.JoinHostPort("localhost", fmt.Sprint(s.port)), slog.LOG_DEBUG, "nitric-run")
102+
if err != nil {
103+
return err
104+
}
105+
log.SetOutput(logwriter)
106+
107+
return nil
108+
}

pkg/containerengine/types.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
"github.com/spf13/viper"
2828
)
2929

30-
var MockEngine ContainerEngine
30+
var DiscoveredEngine ContainerEngine
3131

3232
type Image struct {
3333
ID string `yaml:"id"`
@@ -36,7 +36,14 @@ type Image struct {
3636
CreatedAt string `yaml:"createdAt,omitempty"`
3737
}
3838

39+
type ContainerLogger interface {
40+
Start() error
41+
Stop() error
42+
Config() *container.LogConfig
43+
}
44+
3945
type ContainerEngine interface {
46+
Type() string
4047
Build(dockerfile, path, imageTag string, buildArgs map[string]string) error
4148
ListImages(stackName, containerName string) ([]Image, error)
4249
ImagePull(rawImage string) error
@@ -49,19 +56,21 @@ type ContainerEngine interface {
4956
ContainersListByLabel(match map[string]string) ([]types.Container, error)
5057
RemoveByLabel(name, value string) error
5158
ContainerExec(containerName string, cmd []string, workingDir string) error
59+
Logger(stackPath string) ContainerLogger
5260
}
5361

5462
func Discover() (ContainerEngine, error) {
55-
if MockEngine != nil {
56-
// for unit testing
57-
return MockEngine, nil
63+
if DiscoveredEngine != nil {
64+
return DiscoveredEngine, nil
5865
}
5966
pm, err := newPodman()
6067
if err == nil {
68+
DiscoveredEngine = pm
6169
return pm, nil
6270
}
6371
dk, err := newDocker()
6472
if err == nil {
73+
DiscoveredEngine = dk
6574
return dk, nil
6675
}
6776
return nil, errors.New("neither podman nor docker found")

pkg/run/function.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func (f *Function) Start() error {
7373
Target: "/app",
7474
},
7575
},
76+
LogConfig: *f.ce.Logger(f.runCtx).Config(),
7677
}
7778
if runtime.GOOS == "linux" {
7879
// setup host.docker.internal to route to host gateway

0 commit comments

Comments
 (0)