Repository: dtan4/k8stail
Branch: master
Commit: c3afcb05dee3
Files: 19
Total size: 38.1 KB
Directory structure:
gitextract_b8pp44tf/
├── .dockerignore
├── .github/
│ └── workflows/
│ ├── release.yaml
│ └── test.yaml
├── .gitignore
├── .goreleaser.yml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── counter.yaml
├── go.mod
├── go.sum
├── logger.go
├── main.go
├── renovate.json
├── tail.go
├── version.go
└── watch.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
# Created by https://www.gitignore.io/api/go
### Go ###
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# external packages folder
vendor/
bin/
dist/
.git
.gitignore
README.md
================================================
FILE: .github/workflows/release.yaml
================================================
name: Release
on:
push:
tags:
- "v*.*.*"
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.20
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --rm-dist
env:
# To upload Homebrew recipe to dtan4/homebrew-tools, we need a personal token
# instead of Action's temporary token
GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_TOKEN }}
================================================
FILE: .github/workflows/test.yaml
================================================
name: Test
on:
push:
branches:
- master
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Go
uses: actions/setup-go@v5
with:
cache: true
go-version-file: 'go.mod'
- name: Run tests
run: make ci-test
- name: Send test coverage to Codecov
uses: codecov/codecov-action@v5
- name: Run Trivy vulnerability scanner in repo mode
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: "fs"
ignore-unfixed: true
vuln-type: "os,library"
severity: "CRITICAL,HIGH"
exit-code: "1"
================================================
FILE: .gitignore
================================================
# Created by https://www.gitignore.io/api/go
### Go ###
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# external packages folder
vendor/
/bin/
/dist/
================================================
FILE: .goreleaser.yml
================================================
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
before:
hooks:
- make clean
- go mod tidy
builds:
- ldflags:
- "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
env:
- CGO_ENABLED=0
goos:
- darwin
- linux
- windows
goarch:
- 386
- amd64
- arm
- arm64
archives:
- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
format_overrides:
- goos: windows
format: zip
release:
prerelease: auto
brews:
- tap:
owner: dtan4
name: homebrew-tools
folder: Formula
homepage: https://github.com/dtan4/k8stail
description: "`tail -f` experience for Kubernetes Pods"
skip_upload: auto # skip if the version is rc (e.g. v1.0.0-rc1)
test: |
system "#{bin}/k8stail", "-v"
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- Merge pull request
- Merge branch
================================================
FILE: CHANGELOG.md
================================================
# [v0.7.0](https://github.com/dtan4/k8stail/releases/tag/v0.7.0) (2022-03-05)
- Go 1.17
- Update dependencies
# [v0.6.0](https://github.com/dtan4/k8stail/releases/tag/v0.6.0) (2018-07-29)
## Features
- Enable external auth provider [#40](https://github.com/dtan4/k8stail/pull/40)
## Others
- Use Go 1.10.3 on Travis CI [#42](https://github.com/dtan4/k8stail/pull/42)
- Upgrade to client-go 8.0.0 [#39](https://github.com/dtan4/k8stail/pull/39)
# [v0.5.2.rc1](https://github.com/dtan4/k8stail/releases/tag/v0.5.2.rc1) (2017-04-27)
## Features
- Add debug flag to enable pprof [#31](https://github.com/dtan4/k8stail/pull/31)
## Fixed
- Close goroutine immediately if there is no valid object [#30](https://github.com/dtan4/k8stail/pull/30)
# [v0.5.1](https://github.com/dtan4/k8stail/releases/tag/v0.5.1) (2017-04-27)
## Fixed
- Print selected context correctly [#28](https://github.com/dtan4/k8stail/pull/28)
# [v0.5.0](https://github.com/dtan4/k8stail/releases/tag/v0.5.0) (2017-04-25)
## Features
- Watch Kubernetes events to detect Pod lifecycle correctly [#26](https://github.com/dtan4/k8stail/pull/26)
- Add more short flags [#25](https://github.com/dtan4/k8stail/pull/25) (thanks @atombender)
# [v0.4.0](https://github.com/dtan4/k8stail/releases/tag/v0.4.0) (2017-04-11)
## Features
- Use default namespace set in kubecfg [#21](https://github.com/dtan4/k8stail/pull/21)
- Add `--no-halt` flag [#18](https://github.com/dtan4/k8stail/pull/18)
## Fixed
- Detect container recreation [#20](https://github.com/dtan4/k8stail/pull/20)
# [v0.3.0](https://github.com/dtan4/k8stail/releases/tag/v0.3.0) (2016-12-12)
## Backward incompatible changes
- Deprecate `-flag` style flag, use `--flag` [#11](https://github.com/dtan4/k8stail/pull/11)
## Features
- Support context switch by `--context` flag [#13](https://github.com/dtan4/k8stail/pull/13) (Thanks @apstndb)
# [v0.2.1](https://github.com/dtan4/k8stail/releases/tag/v0.2.1) (2016-11-16)
Rebuilt binaries to be statically-linked.
# [v0.2.0](https://github.com/dtan4/k8stail/releases/tag/v0.2.0) (2016-11-16)
## Features
- Stream logs of all containers in pod [#5](https://github.com/dtan4/k8stail/pull/5)
- Get kubeconfig path from KUBECONFIG [#4](https://github.com/dtan4/k8stail/pull/4)
# [v0.1.0](https://github.com/dtan4/k8stail/releases/tag/v0.1.0) (2016-11-15)
Initial release.
================================================
FILE: Dockerfile
================================================
FROM golang:1.26 AS builder
WORKDIR /go/src/github.com/dtan4/k8stail
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /k8stail
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /k8stail /k8stail
ENTRYPOINT ["/k8stail"]
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Daisuke Fujita
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
NAME := k8stail
VERSION := $(shell git tag | sort -V -r | head -n1)-next
COMMIT := $(shell git rev-parse HEAD)
DATE := $(shell date "+%Y-%m-%dT%H:%M:%S%z")
SRCS := $(shell find . -name '*.go' -type f)
LDFLAGS := -ldflags="-s -w -X \"main.version=$(VERSION)\" -X \"main.commit=$(COMMIT)\" -X \"main.date=$(DATE)\""
.DEFAULT_GOAL := bin/$(NAME)
export GO111MODULE=on
bin/$(NAME): $(SRCS)
go build $(LDFLAGS) -o bin/$(NAME)
.PHONY: ci-test
ci-test:
go test -coverpkg=./... -coverprofile=coverage.txt -v ./...
.PHONY: clean
clean:
rm -rf bin/*
.PHONY: install
install:
go install $(LDFLAGS)
.PHONY: test
test:
go test -cover -v
================================================
FILE: README.md
================================================
# k8stail
[](https://github.com/dtan4/k8stail/actions?query=workflow%3ATest+branch%3Amaster)
[](https://codecov.io/gh/dtan4/k8stail)
[](https://github.com/dtan4/k8stail/releases)
`tail -f` experience for Kubernetes Pods
As you know, `kubectl logs` can stream only ONE pod at the same time. `k8stail` enables you to watch __log streams of ALL pods__ in the specified namespace or labels in real time, like `tail -f`.

## Table of Contents
* [Requirements](#requirements)
* [Installation](#installation)
+ [Using Homebrew (OS X only)](#using-homebrew-os-x-only)
+ [Precompiled binary](#precompiled-binary)
+ [From source](#from-source)
+ [Run in a Docker container](#run-in-a-docker-container)
* [Usage](#usage)
+ [kubeconfig file](#kubeconfig-file)
+ [Options](#options)
* [Development](#development)
* [Author](#author)
* [License](#license)
## Requirements
Kubernetes 1.3 or above
## Installation
### Using Homebrew (OS X only)
Formula is available at [dtan4/homebrew-dtan4](https://github.com/dtan4/homebrew-dtan4).
```bash
$ brew tap dtan4/dtan4
$ brew install k8stail
```
### Precompiled binary
Precompiled binaries for Windows, OS X, Linux are available at [Releases](https://github.com/dtan4/k8stail/releases).
### From source
```bash
$ go get -d github.com/dtan4/k8stail
$ cd $GOPATH/src/github.com/dtan4/k8stail
$ make deps
$ make install
```
### Run in a Docker container
Docker image is no longer provided officially.
If you'd like to run k8sec in Docker image, see [`Dockerfile`](Dockerfile) and build image by yourself.
```bash
docker build -t k8stail .
```
## Usage
Logs of all pods, all containers in pod in the specified namespace are streaming. When new pod is added, logs of the pod also appears.
To stop streaming and exit, press `Ctrl-C`.
```bash
$ k8stail --namespace awesome-app
Namespace: awesome-app
Labels:
----------
Pod awesome-app-web-4212725599-67vd4 has detected
Pod awesome-app-web-4212725599-6pduy has detected
Pod awesome-app-web-4212725599-lbuny has detected
Pod awesome-app-web-4212725599-mh3g1 has detected
Pod awesome-app-web-4212725599-pvjsm has detected
[awesome-app-web-4212725599-mh3g1][web] | creating base compositions...
[awesome-app-web-4212725599-zei9h][web] | (47.1ms) CREATE TABLE "schema_migrations" ("version" character varying NOT NULL)
[awesome-app-web-4212725599-zei9h][web] | (45.1ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
[awesome-app-web-4212725599-zei9h][web] | ActiveRecord::SchemaMigration Load (1.8ms) SELECT "schema_migrations".* FROM "schema_migrations"
[awesome-app-web-4212725599-zei9h][web] | Migrating to CreatePosts (20160218082522)
```
With `--timestamps` option, log timestamp is printed together.
```bash
$ k8stail --namespace awesome-app --timestamps
Namespace: awesome-app
Labels:
----------
Pod awesome-app-web-4212725599-67vd4 has detected
Pod awesome-app-web-4212725599-6pduy has detected
Pod awesome-app-web-4212725599-lbuny has detected
Pod awesome-app-web-4212725599-mh3g1 has detected
Pod awesome-app-web-4212725599-pvjsm has detected
[awesome-app-web-4212725599-mh3g1][web] 2016-11-15T10:57:22.178667425Z | creating base compositions...
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.309011520Z | (47.1ms) CREATE TABLE "schema_migrations" ("version" character varying NOT NULL)
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.309053601Z | (45.1ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.463700110Z | ActiveRecord::SchemaMigration Load (1.8ms) SELECT "schema_migrations".* FROM "schema_migrations"
[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.463743373Z | Migrating to CreatePosts (20160218082522)
```
With `--labels` option, you can filter pods to watch.
```bash
$ k8stail --namespace awesome-app --labels name=awesome-app-web
Namespace: awesome-app
Labels: name=awesome-app-web
----------
Pod awesome-app-web-4212725599-67vd4 has detected
Pod awesome-app-web-4212725599-6pduy has detected
Pod awesome-app-web-4212725599-lbuny has detected
Pod awesome-app-web-4212725599-mh3g1 has detected
Pod awesome-app-web-4212725599-pvjsm has detected
[awesome-app-web-4212725599-mh3g1][web] | creating base compositions...
[awesome-app-web-4212725599-zei9h][web] | (47.1ms) CREATE TABLE "schema_migrations" ("version" character varying NOT NULL)
[awesome-app-web-4212725599-zei9h][web] | (45.1ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version")
[awesome-app-web-4212725599-zei9h][web] | ActiveRecord::SchemaMigration Load (1.8ms) SELECT "schema_migrations".* FROM "schema_migrations"
[awesome-app-web-4212725599-zei9h][web] | Migrating to CreatePosts (20160218082522)
```
### kubeconfig file
`k8stail` uses `~/.kube/config` as default. You can specify another path by `KUBECONFIG` environment variable or `--kubeconfig` option. `--kubeconfig` option always overrides `KUBECONFIG` environment variable.
```bash
$ KUBECONFIG=/path/to/kubeconfig k8stail
# or
$ k8stail --kubeconfig=/path/to/kubeconfig
```
### Options
|Option|Description|Required|Default|
|---------|-----------|-------|-------|
|`--context=CONTEXT`|Kubernetes context|||
|`--debug`|Debug mode using pprof (http://localhost:6060)||`false`|
|`--kubeconfig=KUBECONFIG`|Path of kubeconfig||`~/.kube/config`|
|`--labels=LABELS`|Label filter query (e.g. `app=APP,role=ROLE`)|||
|`--namespace=NAMESPACE`|Kubernetes namespace||`default`|
|`--timestamps`|Include timestamps on each line||`false`|
|`-h`, `-help`|Print command line usage|||
|`-v`, `-version`|Print version|||
## Development
Go 1.7 or above is required.
Clone this repository and build using `make`.
```bash
$ go get -d github.com/dtan4/k8stail
$ cd $GOPATH/src/github.com/dtan4/k8stail
$ make
```
## Author
Daisuke Fujita ([@dtan4](https://github.com/dtan4))
## License
[](LICENSE)
================================================
FILE: counter.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: counter
labels:
app: counter
spec:
replicas: 3
selector:
matchLabels:
app: counter
template:
metadata:
labels:
app: counter
spec:
containers:
- name: counter-a
image: busybox
args: ["/bin/sh", "-c", 'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']
- name: counter-b
image: busybox
args: ["/bin/sh", "-c", 'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']
================================================
FILE: go.mod
================================================
module github.com/dtan4/k8stail
go 1.26.3
require (
github.com/fatih/color v1.19.0
github.com/spf13/pflag v1.0.10
k8s.io/api v0.36.1
k8s.io/apimachinery v0.36.1
k8s.io/client-go v0.36.1
)
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
================================================
FILE: logger.go
================================================
package main
import (
"fmt"
"strings"
"sync"
"github.com/fatih/color"
)
var (
greenBold = color.New(color.FgGreen, color.Bold)
yellowBold = color.New(color.FgYellow, color.Bold)
redBold = color.New(color.FgRed, color.Bold)
boldFunc = color.New(color.Bold).SprintFunc()
yellowFunc = color.New(color.FgYellow).SprintFunc()
)
// Logger represents logger
type Logger struct {
m sync.Mutex
}
// NewLogger returns new Logger object
func NewLogger() *Logger {
return &Logger{
m: sync.Mutex{},
}
}
// PrintColorizedLog prints log with the given color
func (l *Logger) PrintColorizedLog(c *color.Color, line string) {
l.m.Lock()
defer l.m.Unlock()
c.Println(line)
}
// PrintHeader prints header
func (l *Logger) PrintHeader(context, namespace, labels string) {
fmt.Printf("%s %s\n", boldFunc("Context: "), context)
fmt.Printf("%s %s\n", boldFunc("Namespace:"), namespace)
fmt.Printf("%s %s\n", boldFunc("Labels: "), labels)
color.New(color.FgYellow).Println("Press Ctrl-C to exit.")
color.New(color.Bold).Println("----------")
}
// PrintPlainLog prints log with no cosmetics
func (l *Logger) PrintPlainLog(line string) {
l.m.Lock()
defer l.m.Unlock()
fmt.Println(line)
}
// PrintPodDetected prints that Pod was detected
func (l *Logger) PrintPodDetected(pod, container string) {
l.PrintColorizedLog(greenBold, fmt.Sprintf("Pod:%s Container:%s has been detected", pod, container))
}
// PrintPodDeleted prints that Pod was finished
func (l *Logger) PrintPodFinished(pod, container string) {
l.PrintColorizedLog(yellowBold, fmt.Sprintf("Pod:%s Container:%s has been finished", pod, container))
}
// PrintPodDeleted prints that Pod was deleted
func (l *Logger) PrintPodDeleted(pod, container string) {
l.PrintColorizedLog(redBold, fmt.Sprintf("Pod:%s Container:%s has been deleted", pod, container))
}
// PrintPodLog prints Pod log
func (l *Logger) PrintPodLog(pod, container, line string, timestamps bool) {
l.m.Lock()
defer l.m.Unlock()
if timestamps {
ss := strings.SplitN(line, " ", 2)
fmt.Printf("[%s][%s] %s %s %s \n", boldFunc(pod), boldFunc(container), yellowFunc(ss[0]), boldFunc("|"), ss[1])
} else {
fmt.Printf("[%s][%s] %s %s\n", boldFunc(pod), boldFunc(container), boldFunc("|"), line)
}
}
================================================
FILE: main.go
================================================
package main
import (
"context"
"fmt"
"log"
"math"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"time"
flag "github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/tools/clientcmd"
)
const (
debugAddress = ":6060"
logSecondsOffset = 10
)
var (
sinceSeconds = int64(math.Ceil(float64(logSecondsOffset) / float64(time.Second)))
)
func main() {
var (
debug bool
kubeContext string
kubeconfig string
labels string
namespace string
timestamps bool
version bool
)
flags := flag.NewFlagSet("k8stail", flag.ExitOnError)
flags.Usage = func() {
flags.PrintDefaults()
}
flags.StringVar(&kubeContext, "context", "", "Kubernetes context")
flags.BoolVar(&debug, "debug", false, "Debug mode using pprof (http://localhost:6060)")
flags.StringVar(&kubeconfig, "kubeconfig", "", "Path of kubeconfig")
flags.StringVarP(&labels, "labels", "l", "", "Label filter query")
flags.StringVarP(&namespace, "namespace", "n", "", "Kubernetes namespace")
flags.BoolVarP(×tamps, "timestamps", "t", false, "Include timestamps on each line")
flags.BoolVarP(&version, "version", "v", false, "Print version")
if err := flags.Parse(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if kubeconfig == "" {
if os.Getenv("KUBECONFIG") != "" {
kubeconfig = os.Getenv("KUBECONFIG")
} else {
kubeconfig = clientcmd.RecommendedHomeFile
}
}
if version {
printVersion()
os.Exit(0)
}
if debug {
go func() {
log.Println(http.ListenAndServe(debugAddress, nil))
}()
}
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
&clientcmd.ConfigOverrides{CurrentContext: kubeContext})
config, err := clientConfig.ClientConfig()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
rawConfig, err := clientConfig.RawConfig()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
var currentContext string
if kubeContext == "" {
currentContext = rawConfig.CurrentContext
} else {
currentContext = kubeContext
}
if namespace == "" {
if rawConfig.Contexts[currentContext].Namespace == "" {
namespace = metav1.NamespaceDefault
} else {
namespace = rawConfig.Contexts[currentContext].Namespace
}
}
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt)
logger := NewLogger()
logger.PrintHeader(currentContext, namespace, labels)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
watcher, err := clientset.CoreV1().Pods(namespace).Watch(ctx, metav1.ListOptions{
LabelSelector: labels,
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
added, finished, deleted := Watch(ctx, watcher)
tails := NewTailMap()
go func() {
for target := range added {
id := target.GetID()
if _, ok := tails.Get(id); ok {
continue
}
tail := NewTail(target.Namespace, target.Pod, target.Container, logger, sinceSeconds, timestamps)
tails.Set(id, tail)
tail.Start(ctx, clientset)
}
}()
go func() {
for target := range finished {
id := target.GetID()
t, ok := tails.Get(id)
if !ok {
continue
}
if t.Finished {
continue
}
t.Finish()
tails.Delete(id)
}
}()
go func() {
for target := range deleted {
id := target.GetID()
t, ok := tails.Get(id)
if !ok {
continue
}
t.Delete()
tails.Delete(id)
}
}()
<-sigCh
cancel()
}
================================================
FILE: renovate.json
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"schedule:weekly"
],
"postUpdateOptions": [
"gomodTidy"
],
"packageRules": [
{
"matchDatasources": ["golang-version"],
"rangeStrategy": "bump"
},
{
"groupName": "all non-major Go dependencies",
"groupSlug": "all-minor-patch-gomod",
"matchManagers": [
"gomod"
],
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest"
],
"automerge": true,
"automergeType": "branch",
"matchPackageNames": [
"!go"
]
},
{
"groupName": "all non-major GitHub Actions dependencies",
"groupSlug": "all-minor-patch-github-actions",
"matchManagers": [
"github-actions"
],
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest"
],
"automerge": true,
"automergeType": "branch"
},
{
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest"
],
"automerge": true,
"automergeType": "branch"
}
]
}
================================================
FILE: tail.go
================================================
package main
import (
"bufio"
"context"
"fmt"
"os"
"sync"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
)
type Tail struct {
Finished bool
closed chan struct{}
logger *Logger
namespace string
pod string
container string
sinceSeconds int64
timestamps bool
}
// NewTail creates new Tail object
func NewTail(namespace, pod, container string, logger *Logger, sinceSeconds int64, timestamps bool) *Tail {
return &Tail{
Finished: false,
closed: make(chan struct{}),
logger: logger,
namespace: namespace,
pod: pod,
container: container,
sinceSeconds: sinceSeconds,
timestamps: timestamps,
}
}
// Start starts Pod log streaming
func (t *Tail) Start(ctx context.Context, clientset *kubernetes.Clientset) {
t.logger.PrintPodDetected(t.pod, t.container)
go func() {
rs, err := clientset.CoreV1().Pods(t.namespace).GetLogs(t.pod, &v1.PodLogOptions{
Container: t.container,
Follow: true,
SinceSeconds: &t.sinceSeconds,
Timestamps: t.timestamps,
}).Stream(ctx)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
defer rs.Close()
go func() {
<-t.closed
rs.Close()
}()
sc := bufio.NewScanner(rs)
for sc.Scan() {
t.logger.PrintPodLog(t.pod, t.container, sc.Text(), t.timestamps)
}
}()
go func() {
<-ctx.Done()
close(t.closed)
}()
}
// Finish finishes Pod log streaming with Pod completion
func (t *Tail) Finish() {
t.logger.PrintPodFinished(t.pod, t.container)
t.Finished = true
}
// Delete finishes Pod log streaming with Pod deletion
func (t *Tail) Delete() {
t.logger.PrintPodDeleted(t.pod, t.container)
close(t.closed)
}
type TailMap struct {
mu sync.Mutex
data map[string]*Tail
}
func NewTailMap() *TailMap {
return &TailMap{
data: make(map[string]*Tail),
}
}
func (m *TailMap) Get(k string) (*Tail, bool) {
m.mu.Lock()
defer m.mu.Unlock()
d, ok := m.data[k]
return d, ok
}
func (m *TailMap) Set(k string, v *Tail) {
m.mu.Lock()
defer m.mu.Unlock()
m.data[k] = v
}
func (m *TailMap) Delete(k string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.data, k)
}
================================================
FILE: version.go
================================================
package main
import (
"fmt"
)
var (
version string
commit string
date string
)
func printVersion() {
fmt.Printf("k8stail version: %s, commit: %s, build at: %s\n", version, commit, date)
}
================================================
FILE: watch.go
================================================
package main
import (
"context"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/watch"
)
type Target struct {
Namespace string
Pod string
Container string
}
// NewTarget creates new Target object
func NewTarget(namespace, pod, container string) *Target {
return &Target{
Namespace: namespace,
Pod: pod,
Container: container,
}
}
// GetID returns target ID
func (t *Target) GetID() string {
return t.Namespace + "_" + t.Pod + "_" + t.Container
}
// Watch starts and listens Kubernetes Pod events
func Watch(ctx context.Context, watcher watch.Interface) (chan *Target, chan *Target, chan *Target) {
added := make(chan *Target)
finished := make(chan *Target)
deleted := make(chan *Target)
go func() {
for {
select {
case e := <-watcher.ResultChan():
if e.Object == nil {
return
}
pod := e.Object.(*v1.Pod)
switch e.Type {
case watch.Added:
if pod.Status.Phase != v1.PodRunning {
continue
}
for _, container := range pod.Spec.Containers {
added <- NewTarget(pod.Namespace, pod.Name, container.Name)
}
case watch.Modified:
switch pod.Status.Phase {
case v1.PodRunning:
for _, container := range pod.Spec.Containers {
added <- NewTarget(pod.Namespace, pod.Name, container.Name)
}
case v1.PodSucceeded, v1.PodFailed:
for _, container := range pod.Spec.Containers {
finished <- NewTarget(pod.Namespace, pod.Name, container.Name)
}
}
case watch.Deleted:
for _, container := range pod.Spec.Containers {
deleted <- NewTarget(pod.Namespace, pod.Name, container.Name)
}
}
case <-ctx.Done():
watcher.Stop()
return
}
}
}()
return added, finished, deleted
}
gitextract_b8pp44tf/ ├── .dockerignore ├── .github/ │ └── workflows/ │ ├── release.yaml │ └── test.yaml ├── .gitignore ├── .goreleaser.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── counter.yaml ├── go.mod ├── go.sum ├── logger.go ├── main.go ├── renovate.json ├── tail.go ├── version.go └── watch.go
SYMBOL INDEX (27 symbols across 5 files)
FILE: logger.go
type Logger (line 20) | type Logger struct
method PrintColorizedLog (line 32) | func (l *Logger) PrintColorizedLog(c *color.Color, line string) {
method PrintHeader (line 40) | func (l *Logger) PrintHeader(context, namespace, labels string) {
method PrintPlainLog (line 49) | func (l *Logger) PrintPlainLog(line string) {
method PrintPodDetected (line 57) | func (l *Logger) PrintPodDetected(pod, container string) {
method PrintPodFinished (line 62) | func (l *Logger) PrintPodFinished(pod, container string) {
method PrintPodDeleted (line 67) | func (l *Logger) PrintPodDeleted(pod, container string) {
method PrintPodLog (line 72) | func (l *Logger) PrintPodLog(pod, container, line string, timestamps b...
function NewLogger (line 25) | func NewLogger() *Logger {
FILE: main.go
constant debugAddress (line 22) | debugAddress = ":6060"
constant logSecondsOffset (line 23) | logSecondsOffset = 10
function main (line 30) | func main() {
FILE: tail.go
type Tail (line 14) | type Tail struct
method Start (line 40) | func (t *Tail) Start(ctx context.Context, clientset *kubernetes.Client...
method Finish (line 75) | func (t *Tail) Finish() {
method Delete (line 81) | func (t *Tail) Delete() {
function NewTail (line 26) | func NewTail(namespace, pod, container string, logger *Logger, sinceSeco...
type TailMap (line 86) | type TailMap struct
method Get (line 98) | func (m *TailMap) Get(k string) (*Tail, bool) {
method Set (line 107) | func (m *TailMap) Set(k string, v *Tail) {
method Delete (line 114) | func (m *TailMap) Delete(k string) {
function NewTailMap (line 92) | func NewTailMap() *TailMap {
FILE: version.go
function printVersion (line 13) | func printVersion() {
FILE: watch.go
type Target (line 10) | type Target struct
method GetID (line 26) | func (t *Target) GetID() string {
function NewTarget (line 17) | func NewTarget(namespace, pod, container string) *Target {
function Watch (line 31) | func Watch(ctx context.Context, watcher watch.Interface) (chan *Target, ...
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (42K chars).
[
{
"path": ".dockerignore",
"chars": 476,
"preview": "\n# Created by https://www.gitignore.io/api/go\n\n### Go ###\n# Compiled Object files, Static and Dynamic libs (Shared Objec"
},
{
"path": ".github/workflows/release.yaml",
"chars": 669,
"preview": "name: Release\non:\n push:\n tags:\n - \"v*.*.*\"\n\njobs:\n goreleaser:\n runs-on: ubuntu-latest\n steps:\n - "
},
{
"path": ".github/workflows/test.yaml",
"chars": 762,
"preview": "name: Test\n\non:\n push:\n branches:\n - master\n pull_request:\n\njobs:\n test:\n runs-on: ubuntu-latest\n steps"
},
{
"path": ".gitignore",
"chars": 451,
"preview": "\n# Created by https://www.gitignore.io/api/go\n\n### Go ###\n# Compiled Object files, Static and Dynamic libs (Shared Objec"
},
{
"path": ".goreleaser.yml",
"chars": 1191,
"preview": "# This is an example goreleaser.yaml file with some sane defaults.\n# Make sure to check the documentation at http://gore"
},
{
"path": "CHANGELOG.md",
"chars": 2368,
"preview": "# [v0.7.0](https://github.com/dtan4/k8stail/releases/tag/v0.7.0) (2022-03-05)\n\n- Go 1.17\n- Update dependencies\n\n# [v0.6."
},
{
"path": "Dockerfile",
"chars": 265,
"preview": "FROM golang:1.26 AS builder\n\nWORKDIR /go/src/github.com/dtan4/k8stail\n\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY ."
},
{
"path": "LICENSE",
"chars": 1081,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Daisuke Fujita\n\nPermission is hereby granted, free of charge, to any person ob"
},
{
"path": "Makefile",
"chars": 647,
"preview": "NAME := k8stail\nVERSION := $(shell git tag | sort -V -r | head -n1)-next\nCOMMIT := $(shell git rev-parse HEAD)\nDATE "
},
{
"path": "README.md",
"chars": 6335,
"preview": "# k8stail\n\n[](https://github.com/dtan4/k8sta"
},
{
"path": "counter.yaml",
"chars": 543,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: counter\n labels:\n app: counter\nspec:\n replicas: 3\n selector"
},
{
"path": "go.mod",
"chars": 2095,
"preview": "module github.com/dtan4/k8stail\n\ngo 1.26.3\n\nrequire (\n\tgithub.com/fatih/color v1.19.0\n\tgithub.com/spf13/pflag v1.0.10\n\tk"
},
{
"path": "go.sum",
"chars": 10859,
"preview": "github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go"
},
{
"path": "logger.go",
"chars": 2255,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/fatih/color\"\n)\n\nvar (\n\tgreenBold = color.New(color.FgGre"
},
{
"path": "main.go",
"chars": 3702,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\tfla"
},
{
"path": "renovate.json",
"chars": 1189,
"preview": "{\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n \"extends\": [\n \"config:recommended\",\n \"schedu"
},
{
"path": "tail.go",
"chars": 2161,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/kubernetes\""
},
{
"path": "version.go",
"chars": 200,
"preview": "package main\n\nimport (\n\t\"fmt\"\n)\n\nvar (\n\tversion string\n\tcommit string\n\tdate string\n)\n\nfunc printVersion() {\n\tfmt.Pri"
},
{
"path": "watch.go",
"chars": 1752,
"preview": "package main\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n)\n\ntype Target struct {\n\tNames"
}
]
About this extraction
This page contains the full source code of the dtan4/k8stail GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (38.1 KB), approximately 15.1k tokens, and a symbol index with 27 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.