diff --git a/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/BuilderModel.java b/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/BuilderModel.java
index 79ad01bb..81556b60 100644
--- a/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/BuilderModel.java
+++ b/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/BuilderModel.java
@@ -40,6 +40,10 @@ public List<PropertyModel> prefixParameters() {
 		return properties.stream().filter(p -> p.kind == PropertyKind.NAME_PARAMETER).toList();
 	}
 
+	public List<String> descriptionLines() {
+		return description.lines().map(String::trim).toList();
+	}
+
 	record Converter(String methodName) {
 	}
 
@@ -68,19 +72,29 @@ public String propertyLiteral() {
 			return "PROPERTY_" + name;
 		}
 
-		public String convertMethod() {
+		public @Nullable String convertMethod() {
 			if (converter != null) {
 				return ".map(_v -> " + converter.methodName + "(_v))";
 			}
 			return switch (type) {
 				case INTEGER_TYPE -> ".toInt()";
-				case STRING_TYPE -> "";
+				case STRING_TYPE -> null;
 				case URI_TYPE -> ".toURI()";
 				case BOOLEAN_TYPE -> ".toBoolean()";
 				default -> throw new IllegalStateException(type + " is not supported");
 			};
 		}
 
+		public String typeDescription() {
+			return switch (type) {
+				case INTEGER_TYPE -> "Integer";
+				case STRING_TYPE -> "String";
+				case URI_TYPE -> "URI";
+				case BOOLEAN_TYPE -> "Boolean";
+				default -> "String (converted)";
+			};
+		}
+
 		boolean isLiteralType() {
 			return switch (type) {
 				case INTEGER_TYPE, STRING_TYPE, BOOLEAN_TYPE -> true;
diff --git a/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/ConfigProcessor.java b/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/ConfigProcessor.java
index a8318e97..14b7da46 100644
--- a/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/ConfigProcessor.java
+++ b/rainbowgum-config-apt/src/main/java/io/jstach/rainbowgum/apt/ConfigProcessor.java
@@ -248,7 +248,7 @@ else if (inDescription) {
 					if (parts.length >= 3) {
 						String paramName = parts[1];
 						String paramDescription = parts[2];
-						properties.put(paramName, paramDescription);
+						properties.put(paramName, processParamDescription(paramDescription));
 					}
 				}
 			}
@@ -256,6 +256,21 @@ else if (inDescription) {
 		}
 	}
 
+	private static String processParamDescription(String description) {
+		String s = capitalizeFirstLetter(description);
+		if (!s.endsWith(".")) {
+			return s + ".";
+		}
+		return s;
+	}
+
+	private static String capitalizeFirstLetter(String str) {
+		if (str.isEmpty()) {
+			return str;
+		}
+		return str.trim().substring(0, 1).toUpperCase() + str.substring(1);
+	}
+
 	private static Class<?> primitiveToClass(TypeKind k) {
 		if (!k.isPrimitive())
 			throw new IllegalArgumentException("k is not primitive: " + k);
diff --git a/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java b/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java
index 81fd0137..9d5921c4 100644
--- a/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java
+++ b/rainbowgum-config-apt/src/main/resources/io/jstach/rainbowgum/apt/ConfigBuilder.java
@@ -5,10 +5,12 @@
 import io.jstach.rainbowgum.LogProperties.Property;
 
 /**
- * Builder to create $$targetType$$.
- * $$description$$
+ * Builder to create {@link $$targetType$$ }.
+ $$#descriptionLines$$
+ * $$.$$
+ $$/descriptionLines$$
  * <table class="table">
- * <caption>Properties retrieved from LogProperties</caption>
+ * <caption><strong>Properties retrieved from LogProperties</strong></caption>
  * <tr>
  * <th>Property Pattern</th>
  * <th>Type</th>
@@ -20,7 +22,7 @@
  $$#normal$$
  * <tr>
  * <td><code>{@value $$propertyLiteral$$ }</code></td>
- * <td><code>$$type$$</code></td>
+ * <td><code>$$typeDescription$$</code></td>
  * <td><code>$$required$$</code></td>
  * <td>$$defaultValueDoc$$</td>
  * <td>$$javadoc$$</td>
@@ -80,8 +82,10 @@ public final class $$builderName$$ {
 		$$#properties$$
 		$$#normal$$
 		$$propertyVar$$ = Property.builder()
-			$$convertMethod$$
+			$$#convertMethod$$$$.$$
+			$$/convertMethod$$
 			.build(LogProperties.interpolateKey($$propertyLiteral$$, prefixParameters));
+
 		$$/normal$$
 		$$#prefixParameter$$
 		this.$$name$$ = $$name$$;
@@ -94,6 +98,7 @@ public final class $$builderName$$ {
 	
 	/**
 	 * Sets $$#required$$<strong>required</strong> $$/required$$$$name$$.
+	 * $$javadoc$$
 	 * Default is $$defaultValueDoc$$.
 	 * @param $$name$$ <code>{@value #$$propertyLiteral$$ } = $$type$$</code> $$javadoc$$
 	 * @return this builder.
diff --git a/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQOutput.java b/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQOutput.java
index d5cccfe4..112851cb 100644
--- a/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQOutput.java
+++ b/rainbowgum-rabbitmq/src/main/java/io/jstach/rainbowgum/rabbitmq/RabbitMQOutput.java
@@ -82,7 +82,7 @@ public class RabbitMQOutput implements LogOutput {
 	}
 
 	/**
-	 * Creates a RabbitMQOutput. This method is called by the generated builder.
+	 * Creates a RabbitMQOutput.
 	 * @param name used to resolve config and give the output a name.
 	 * @param uri passed to the rabbitmq connection factory.
 	 * @param exchange exchange to send messages to.
@@ -103,7 +103,7 @@ static RabbitMQOutput of( //
 			@LogConfigurable.PrefixParameter String name, //
 			@Nullable URI uri, //
 			@LogConfigurable.DefaultParameter("DEFAULT_EXCHANGE") String exchange, //
-			@Nullable String routingKey, //
+			@LogConfigurable.ConvertParameter("toRoutingKeyFunction") @Nullable Function<LogEvent, String> routingKey, //
 			@Nullable Boolean declareExchange, //
 			@Nullable String host, //
 			@Nullable String username, //
@@ -142,7 +142,7 @@ static RabbitMQOutput of( //
 		}
 		Function<LogEvent, String> routingKeyFunction;
 		if (routingKey != null) {
-			routingKeyFunction = e -> routingKey;
+			routingKeyFunction = routingKey;
 		}
 		else {
 			routingKeyFunction = e -> e.level().name();
@@ -151,6 +151,10 @@ static RabbitMQOutput of( //
 				exchangeType);
 	}
 
+	static Function<LogEvent, String> toRoutingKeyFunction(String routingKey) {
+		return e -> routingKey;
+	}
+
 	@Override
 	public void start(LogConfig config) {
 		lock.writeLock().lock();