Skip to content

Commit 27808e4

Browse files
jgulottaJacob Gulotta
andauthored
log friendlier cause for k8s pod health (#67)
Co-authored-by: Jacob Gulotta <jacob.gulotta@gettyimages.com>
1 parent 1bdf1a8 commit 27808e4

File tree

1 file changed

+99
-1
lines changed

1 file changed

+99
-1
lines changed

health-k8s/src/main/scala/com/devsisters/shardcake/K8sPodsHealth.scala

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
11
package com.devsisters.shardcake
22

3+
import com.coralogix.zio.k8s.client.{
4+
DecodedFailure,
5+
DeserializationFailure,
6+
Gone,
7+
HttpFailure,
8+
InvalidEvent,
9+
K8sFailure,
10+
K8sRequestInfo,
11+
NotFound,
12+
RequestFailure,
13+
Unauthorized,
14+
UndefinedField
15+
}
316
import com.coralogix.zio.k8s.client.model.FieldSelector
417
import com.coralogix.zio.k8s.client.v1.pods.Pods
518
import com.devsisters.shardcake.interfaces.{ Logging, PodsHealth }
19+
import com.coralogix.zio.k8s.model.pkg.apis.meta.v1.Status
620
import zio._
721
import zio.cache.{ Cache, Lookup }
822

23+
import java.lang
24+
925
object K8sPodsHealth {
1026

1127
/**
@@ -31,11 +47,93 @@ object K8sPodsHealth {
3147
.map(_.isDefined)
3248
.tap(ZIO.unless(_)(logger.logWarning(s"$podAddress is not found in k8s")))
3349
.catchAllCause(cause =>
34-
logger.logError(s"Error communicating with k8s ${cause.prettyPrint}").as(true)
50+
logger.logError(s"Error communicating with k8s ${cause.map(asException).prettyPrint}").as(true)
3551
)
3652
}
3753
)
3854
} yield new PodsHealth {
3955
def isAlive(podAddress: PodAddress): UIO[Boolean] = cache.get(podAddress)
4056
}).toLayer
57+
58+
// K8sFailure is not an Exception which means an attempt to directly log it as a Cause will, by default, lose transitive causal information
59+
// because we only log k8s communication failures and then report the pod as healthy, we want to propagate as much information as possible to users
60+
// therefore we convert K8sFailure to an exception, keeping transitive causes in some form whenever they're available, and creating friendlier messages for the rest
61+
private def asException(k8sFailure: K8sFailure) =
62+
k8sFailure match {
63+
case Unauthorized(requestInfo, message) =>
64+
new K8sException(s"unauthorized trying to ${toLogString(requestInfo)}: $message")
65+
66+
case HttpFailure(requestInfo, message, code) =>
67+
new K8sException(s"http failure $code trying to ${toLogString(requestInfo)}: $message")
68+
69+
case DecodedFailure(requestInfo, status, code) =>
70+
new K8sException(s"decoded failure $code trying to ${toLogString(requestInfo)}: ${toLogString(status)}")
71+
72+
case DeserializationFailure(requestInfo, error) =>
73+
error.tail.foldLeft(
74+
new K8sException(s"deserialization failure trying to ${toLogString(requestInfo)}", error.head)
75+
) { (result, e) =>
76+
result.addSuppressed(e)
77+
result
78+
}
79+
80+
case RequestFailure(requestInfo, reason) =>
81+
new K8sException(s"request failure trying to ${toLogString(requestInfo)}", reason)
82+
83+
case InvalidEvent(requestInfo, eventType) =>
84+
new K8sException(s"invalid event $eventType trying to ${toLogString(requestInfo)}")
85+
86+
case UndefinedField(field) =>
87+
new K8sException(s"undefined field $field")
88+
89+
case Gone =>
90+
new K8sException("gone")
91+
92+
case NotFound =>
93+
new K8sException("not found")
94+
}
95+
96+
private def toLogString(requestInfo: K8sRequestInfo) = {
97+
val sb = new lang.StringBuilder()
98+
99+
sb.append(requestInfo.operation)
100+
101+
sb.append(' ')
102+
103+
val typ = requestInfo.resourceType
104+
105+
sb.append(typ.group)
106+
sb.append('/')
107+
sb.append(typ.version)
108+
109+
sb.append(' ')
110+
111+
requestInfo.namespace.foreach { ns =>
112+
sb.append(ns)
113+
sb.append('/')
114+
}
115+
sb.append(typ.resourceType)
116+
requestInfo.name.foreach { n =>
117+
sb.append('/')
118+
sb.append(n)
119+
}
120+
121+
requestInfo.labelSelector.foreach { ls =>
122+
sb.append(' ')
123+
sb.append(ls.asQuery)
124+
}
125+
126+
requestInfo.fieldSelector.foreach { fs =>
127+
sb.append(' ')
128+
sb.append(fs.asQuery)
129+
}
130+
131+
sb.toString
132+
}
133+
134+
private def toLogString(status: Status) =
135+
status.message.getOrElse(status.reason.getOrElse("no explanation from k8s"))
136+
137+
private class K8sException(message: String, cause: Throwable = null)
138+
extends RuntimeException(message, cause, true, false)
41139
}

0 commit comments

Comments
 (0)