diff --git a/src/Tree/Controllers/TreeController.cs b/src/Tree/Controllers/TreeController.cs
index 8c74eef60fbae3d93781da917332028418f14883..9a8074d5ea1738bbb1a6578696d7155c4b3dfbef 100644
--- a/src/Tree/Controllers/TreeController.cs
+++ b/src/Tree/Controllers/TreeController.cs
@@ -37,10 +37,10 @@ namespace Coscine.Api.Tree.Controllers
         private readonly ProjectRoleModel _projectRoleModel;
         private readonly ProjectResourceModel _projectResourceModel;
         private readonly CoscineLogger _coscineLogger;
-        private readonly AnalyticsLogObject _analyticsLogObject;
         private readonly string _blobApiLink;
         private readonly string _prefix;
         private readonly IConfiguration _configuration;
+        private readonly string _shaclPropertyUrl = "http://www.w3.org/ns/shacl#property";
 
         /// <summary>
         /// Tree controller constructor
@@ -56,11 +56,10 @@ namespace Coscine.Api.Tree.Controllers
             _projectResourceModel = new ProjectResourceModel();
 
             _coscineLogger = new CoscineLogger(logger);
-            _analyticsLogObject = new AnalyticsLogObject();
 
             var rule = _configuration.GetStringAndWait("traefik/frontends/Coscine.Api.Blob/routes/Coscine.Api.Blob/rule");
-            var host = rule.Substring("Host:".Length, rule.IndexOf(";") - "Host:".Length);
-            var path = rule.Substring(rule.IndexOf("/"));
+            var host = rule["Host:".Length..rule.IndexOf(";")];
+            var path = rule[rule.IndexOf("/")..];
             _blobApiLink = $"https://{host}{path}/blob/";
             _prefix = _configuration.GetStringAndWait("coscine/global/epic/prefix");
         }
@@ -94,6 +93,11 @@ namespace Coscine.Api.Tree.Controllers
             var user = _authenticator.GetUser();
             var check = CheckResourceIdAndPath(resourceId, path, out Resource resource);
 
+            if (resource.ApplicationProfile[^1] != '/')
+            {
+                resource.ApplicationProfile += '/';
+            }
+
             if (user == null || !_resourceModel.HasAccess(user, resource, UserRoles.Owner, UserRoles.Member))
             {
                 return Forbid("User has no Access to this resource.");
@@ -115,13 +119,14 @@ namespace Coscine.Api.Tree.Controllers
 
                 var infos = await resourceTypeDefinition.ListEntries(resourceId, path, resourceTypeOptions);
                 var graphs = new List<JToken>();
-
+                int metadataCount = 0;
                 foreach (var info in infos)
                 {
                     var id = GenerateId(resourceId, info.Key);
                     if (_rdfStoreConnector.HasGraph(id.AbsoluteUri))
                     {
                         var graph = _rdfStoreConnector.GetGraph(id);
+                        metadataCount = graph.Triples.Count;
                         graphs.Add(JToken.Parse(VDS.RDF.Writing.StringWriter.Write(graph, new RdfJsonWriter())));
                     }
                 }
@@ -133,10 +138,10 @@ namespace Coscine.Api.Tree.Controllers
                         {
                             var objectMetaInfo = new ObjectMetaInfo
                             {
-                                Name = x.Key.Substring(x.Key.LastIndexOf("/") + 1),
+                                Name = x.Key[(x.Key.LastIndexOf("/") + 1)..],
                                 Path = x.Key,
                                 Size = (int)x.BodyBytes,
-                                Kind = x.Key.Substring(x.Key.LastIndexOf(".") + 1),
+                                Kind = x.Key[(x.Key.LastIndexOf(".") + 1)..],
                                 Provider = resource.Type.DisplayName
                             };
                             var objectMetaInfoReturnObject = new ObjectMetaInfoReturnObject(objectMetaInfo, _blobApiLink, resource.Id.ToString());
@@ -170,10 +175,14 @@ namespace Coscine.Api.Tree.Controllers
                             };
                             return result;
                         })))
-                        ))
-                    );
+                    ))
+                );
+
+                if (CoscineLoggerConfiguration.IsLogLevelActivated(LogType.Analytics) && path != "/")
+                {
+                    LogAnalyticsViewMd(_projectResourceModel.GetProjectForResource(resource.Id).Value, resource.Id, path, user, GetMetadataCompleteness(metadataCount, resource));
+                }
 
-                LogAnalytics("View MD", resourceId, path, user);
                 return Json(jObject);
             }
             catch
@@ -213,10 +222,11 @@ namespace Coscine.Api.Tree.Controllers
                 return Forbid("User is no project member!");
             }
 
-            if (resource.ApplicationProfile[resource.ApplicationProfile.Length - 1] != '/')
+            if (resource.ApplicationProfile[^1] != '/')
             {
                 resource.ApplicationProfile += '/';
             }
+
             json[graphNameUri.AbsoluteUri]["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] = new JArray
                 {
                     new JObject
@@ -282,16 +292,25 @@ namespace Coscine.Api.Tree.Controllers
             {
                 return BadRequest("Data has the wrong format!");
             }
+
             // store the data
             if (_rdfStoreConnector.HasGraph(graphNameUri))
             {
                 _rdfStoreConnector.ClearGraph(graphNameUri);
-                LogAnalytics("Update MD", resourceId, path, user);
+
+                if (CoscineLoggerConfiguration.IsLogLevelActivated(LogType.Analytics))
+                {
+                    LogAnalyticsUpdateMd(_projectResourceModel.GetProjectForResource(resource.Id).Value, resource.Id, path, user, GetMetadataCompleteness(graph.Triples.Count, resource));
+                }
             }
             else
             {
                 _rdfStoreConnector.CreateNamedGraph(graphNameUri);
-                LogAnalytics("Upload MD", resourceId, path, user);
+
+                if (CoscineLoggerConfiguration.IsLogLevelActivated(LogType.Analytics))
+                {
+                    LogAnalyticsUploadMd(_projectResourceModel.GetProjectForResource(resource.Id).Value, resource.Id, path, user, GetMetadataCompleteness(graph.Triples.Count, resource));
+                }
             }
 
             // BaseUri must be set for the sparql query
@@ -351,25 +370,67 @@ namespace Coscine.Api.Tree.Controllers
             return null;
         }
 
-        /// <summary>
-        /// Log analytics
-        /// </summary>
-        /// <param name="operation">Operation</param>
-        /// <param name="resourceId">Resource of the id</param>
-        /// <param name="filename">Name of the file</param>
-        /// <param name="user">User object</param>
-        private void LogAnalytics(string operation, string resourceId, string filename, User user)
+        private void LogAnalyticsViewMd(Guid projectId, Guid resourceId, string filename, User user, string metadataCompletness)
         {
-            if (CoscineLoggerConfiguration.IsLogLevelActivated(LogType.Analytics))
+            var analyticsLogObject = new AnalyticsLogObject
             {
-                _analyticsLogObject.Type = "Action";
-                _analyticsLogObject.Operation = operation;
-                _analyticsLogObject.FileId = resourceId + "/" + HttpUtility.UrlDecode(filename).Substring(1);
-                _analyticsLogObject.ResourceId = resourceId;
-                _analyticsLogObject.ProjectId = _projectResourceModel.GetProjectForResource(new Guid(resourceId)).ToString();
-                _analyticsLogObject.RoleId = _projectRoleModel.GetGetUserRoleForProject(new Guid(_analyticsLogObject.ProjectId), user.Id).ToString();
-                _coscineLogger.AnalyticsLog(_analyticsLogObject);
-            }
+                Type = "Action",
+                Operation = "View MD",
+                RoleId = _projectRoleModel.GetGetUserRoleForProject(projectId, user.Id).ToString(),
+                ProjectId = projectId.ToString(),
+                ResourceId = resourceId.ToString(),
+                FileId = resourceId.ToString() + "/" + HttpUtility.UrlDecode(filename)[1..],
+                ApplicationsProfile = _resourceModel.CreateReturnObjectFromDatabaseObject(_resourceModel.GetById(resourceId)).ApplicationProfile,
+                MetadataCompleteness = metadataCompletness,
+            };
+                
+            _coscineLogger.AnalyticsLog(analyticsLogObject);
+        }
+
+        private void LogAnalyticsUploadMd(Guid projectId, Guid resourceId, string filename, User user, string metadataCompletness)
+        {
+            var analyticsLogObject = new AnalyticsLogObject
+            {
+                Type = "Action",
+                Operation = "Upload MD",
+                RoleId = _projectRoleModel.GetGetUserRoleForProject(projectId, user.Id).ToString(),
+                ProjectId = projectId.ToString(),
+                ResourceId = resourceId.ToString(),
+                FileId = resourceId.ToString() + "/" + HttpUtility.UrlDecode(filename)[1..],
+                ApplicationsProfile = _resourceModel.CreateReturnObjectFromDatabaseObject(_resourceModel.GetById(resourceId)).ApplicationProfile,
+                MetadataCompleteness = metadataCompletness,
+            };
+
+            _coscineLogger.AnalyticsLog(analyticsLogObject);
+        }
+
+        private void LogAnalyticsUpdateMd(Guid projectId, Guid resourceId, string filename, User user, string metadataCompletness)
+        {
+            var analyticsLogObject = new AnalyticsLogObject
+            {
+                Type = "Action",
+                Operation = "Update MD",
+                RoleId = _projectRoleModel.GetGetUserRoleForProject(projectId, user.Id).ToString(),
+                ProjectId = projectId.ToString(),
+                ResourceId = resourceId.ToString(),
+                FileId = resourceId.ToString() + "/" + HttpUtility.UrlDecode(filename)[1..],
+                ApplicationsProfile = _resourceModel.CreateReturnObjectFromDatabaseObject(_resourceModel.GetById(resourceId)).ApplicationProfile,
+                MetadataCompleteness = metadataCompletness,
+            };
+
+            _coscineLogger.AnalyticsLog(analyticsLogObject);
+        }
+
+        private string GetMetadataCompleteness(int metadataCount, Resource resource)
+        {
+            var shapesGraph = _rdfStoreConnector.GetGraph(resource.ApplicationProfile);
+            var nodeFactory = new NodeFactory();
+            var uriNode = (UriNode)nodeFactory.CreateUriNode(new Uri(_shaclPropertyUrl));
+
+            var total = shapesGraph.GetTriplesWithPredicate(uriNode).Count();
+            var present = metadataCount;
+
+            return $"{present}/{total}";
         }
     }
 }
diff --git a/src/Tree/Tree.csproj b/src/Tree/Tree.csproj
index b475ad4e81f624c54384bbfb20dbea10b236c142..3580436b75f0a683f7575d633d9d56057d7cf6db 100644
--- a/src/Tree/Tree.csproj
+++ b/src/Tree/Tree.csproj
@@ -18,6 +18,8 @@
 	</PropertyGroup>
 	<ItemGroup>
 		<PackageReference Include="Coscine.ApiCommons" Version="2.*-*" />
+		<PackageReference Include="Coscine.Database" Version="2.*-*" />
+		<PackageReference Include="Coscine.Logging" Version="2.*-*" />
 		<PackageReference Include="Coscine.Metadata" Version="2.*-*" />
 		<PackageReference Include="Coscine.ResourceLoader" Version="2.*-*" />
 		<PackageReference Include="Coscine.WaterbutlerHelper" Version="2.*-*" />